[MERGE] Merged lp:~openerp-dev/openobject-addons/trunk-website-al
bzr revid: psa@tinyerp.com-20131111063123-qysh7oyiscfy8ku9
This commit is contained in:
commit
be9553d6f1
|
@ -155,7 +155,6 @@
|
|||
<field name="type" ref="event_type_4"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="address_id" ref="base.res_partner_2"/>
|
||||
<field name="organizer_id" ref="base.res_partner_address_4"/>
|
||||
<field name="description"><![CDATA[
|
||||
<div class="oe_structure">
|
||||
<center><strong>5-days Technical Training</strong></center>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import cStringIO
|
||||
import contextlib
|
||||
import hashlib
|
||||
|
@ -7,7 +6,6 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from sys import maxint
|
||||
|
||||
|
@ -18,14 +16,6 @@ import werkzeug.utils
|
|||
import werkzeug.wrappers
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
from slugify import slugify
|
||||
except ImportError:
|
||||
def slugify(s, max_length=None):
|
||||
spaceless = re.sub(r'\s+', '-', s)
|
||||
specialless = re.sub(r'[^-_a-z0-9]', '', spaceless)
|
||||
return specialless[:max_length]
|
||||
|
||||
import openerp
|
||||
from openerp.osv import fields
|
||||
from openerp.addons.website.models import website
|
||||
|
@ -38,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||
def auth_method_public():
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.db)
|
||||
if not request.session.uid:
|
||||
request.uid = registry['website'].get_public_user().id
|
||||
request.uid = registry['website'].get_public_user(request.cr, openerp.SUPERUSER_ID, request.context).id
|
||||
else:
|
||||
request.uid = request.session.uid
|
||||
http.auth_methods['public'] = auth_method_public
|
||||
|
@ -49,13 +39,25 @@ MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
|
|||
class Website(openerp.addons.web.controllers.main.Home):
|
||||
@website.route('/', type='http', auth="public", multilang=True)
|
||||
def index(self, **kw):
|
||||
return self.page("website.homepage")
|
||||
# TODO: check if plain SQL is needed
|
||||
menu = request.registry['website.menu']
|
||||
root_domain = [('parent_id', '=', False)] # TODO: multiwebsite ('website_id', '=', request.website.id),
|
||||
root_id = menu.search(request.cr, request.uid, root_domain, limit=1, context=request.context)[0]
|
||||
first_menu = menu.search_read(
|
||||
request.cr, request.uid, [('parent_id', '=', root_id)], ['url'],
|
||||
limit=1, order='sequence', context=request.context)
|
||||
if first_menu:
|
||||
first_menu = first_menu[0]['url']
|
||||
if first_menu and first_menu != '/':
|
||||
return request.redirect(first_menu)
|
||||
else:
|
||||
return self.page("website.homepage")
|
||||
|
||||
@website.route('/pagenew/<path:path>', type='http', auth="user")
|
||||
def pagenew(self, path, noredirect=NOPE):
|
||||
module = 'website'
|
||||
# completely arbitrary max_length
|
||||
idname = slugify(path, max_length=50)
|
||||
idname = http.slugify(path, max_length=50)
|
||||
|
||||
request.cr.execute('SAVEPOINT pagenew')
|
||||
imd = request.registry['ir.model.data']
|
||||
|
@ -124,11 +126,8 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
values = {
|
||||
'path': path,
|
||||
}
|
||||
try:
|
||||
html = request.website.render(path, values)
|
||||
except ValueError:
|
||||
html = request.website.render('website.404', values)
|
||||
return html
|
||||
|
||||
return request.website.render(path, values)
|
||||
|
||||
@website.route('/website/customize_template_toggle', type='json', auth='user')
|
||||
def customize_template_set(self, view_id):
|
||||
|
@ -269,12 +268,24 @@ class Website(openerp.addons.web.controllers.main.Home):
|
|||
|
||||
@website.route(['/robots.txt'], type='http', auth="public")
|
||||
def robots(self):
|
||||
return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
|
||||
body = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
|
||||
return request.make_response(body, headers=[('Content-Type', 'text/plain')])
|
||||
|
||||
@website.route(['/sitemap.xml'], type='http', auth="public")
|
||||
@website.route('/sitemap', type='http', auth='public', multilang=True)
|
||||
def sitemap(self):
|
||||
return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
|
||||
|
||||
@website.route('/sitemap.xml', type='http', auth="public")
|
||||
def sitemap_xml(self):
|
||||
body = request.website.render('website.sitemap_xml', {
|
||||
'pages': request.website.list_pages()
|
||||
})
|
||||
|
||||
return request.make_response(body, [
|
||||
('Content-Type', 'application/xml;charset=utf-8')
|
||||
])
|
||||
|
||||
|
||||
class Images(http.Controller):
|
||||
def placeholder(self, response):
|
||||
# file_open may return a StringIO. StringIO can be closed but are
|
||||
|
|
|
@ -8,6 +8,24 @@
|
|||
<field name="default_lang_id" ref="base.lang_en"/>
|
||||
</record>
|
||||
|
||||
<record id="main_menu" model="website.menu">
|
||||
<field name="name">Top Menu</field>
|
||||
</record>
|
||||
|
||||
<record id="menu_homepage" model="website.menu">
|
||||
<field name="name">Home</field>
|
||||
<field name="url">/</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">10</field>
|
||||
</record>
|
||||
|
||||
<record id="menu_contactus" model="website.menu">
|
||||
<field name="name">Contact us</field>
|
||||
<field name="url">/page/website.contactus</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">20</field>
|
||||
</record>
|
||||
|
||||
<record id="public_user" model="res.users">
|
||||
<field name="name">public</field>
|
||||
<field name="login">public</field>
|
||||
|
|
|
@ -9,5 +9,8 @@
|
|||
<field name="social_linkedin">http://www.linkedin.com/company/openerp</field>
|
||||
</record>
|
||||
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="rml_header1">Great Product for Great People</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -131,7 +131,9 @@ class view(osv.osv):
|
|||
arch_section = html.fromstring(
|
||||
value, parser=html.HTMLParser(encoding='utf-8'))
|
||||
|
||||
self._normalize_urls(arch_section)
|
||||
# TODO fme: Temporary desactivated because this breaks most of the snippets
|
||||
# Need to find another way to normalize multilang urls (postprocessing) ?
|
||||
# self._normalize_urls(arch_section)
|
||||
|
||||
if xpath is None:
|
||||
# value is an embedded field on its own, not a view section
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import math
|
||||
import itertools
|
||||
import traceback
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
import simplejson
|
||||
import werkzeug
|
||||
import werkzeug.exceptions
|
||||
import werkzeug.wrappers
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.exceptions import AccessError, AccessDenied
|
||||
from openerp.osv import orm, osv, fields
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
import urllib
|
||||
from urlparse import urljoin
|
||||
import math
|
||||
import traceback
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.exceptions import AccessError, AccessDenied
|
||||
import werkzeug
|
||||
from openerp.addons.base.ir.ir_qweb import QWebException
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def route(routes, *route_args, **route_kwargs):
|
||||
def decorator(f):
|
||||
new_routes = routes if isinstance(routes, list) else [routes]
|
||||
f.cms = True
|
||||
f.multilang = route_kwargs.get('multilang', False)
|
||||
if f.multilang:
|
||||
route_kwargs.pop('multilang')
|
||||
|
@ -33,47 +40,46 @@ def route(routes, *route_args, **route_kwargs):
|
|||
request.route_lang = kwargs.get('lang_code', None)
|
||||
if not hasattr(request, 'website'):
|
||||
request.multilang = f.multilang
|
||||
request.website = request.registry['website'].get_current()
|
||||
# TODO: Select website, currently hard coded
|
||||
request.website = request.registry['website'].browse(
|
||||
request.cr, request.uid, 1, context=request.context)
|
||||
|
||||
if request.route_lang:
|
||||
lang_ok = [lg.code for lg in request.website.language_ids if lg.code == request.route_lang]
|
||||
if not lang_ok:
|
||||
return request.not_found()
|
||||
request.website.preprocess_request(*args, **kwargs)
|
||||
request.website.preprocess_request(request)
|
||||
return f(*args, **kwargs)
|
||||
return wrap
|
||||
return decorator
|
||||
|
||||
def auth_method_public():
|
||||
registry = openerp.modules.registry.RegistryManager.get(request.db)
|
||||
if not request.session.uid:
|
||||
request.uid = registry['website'].get_public_user().id
|
||||
else:
|
||||
request.uid = request.session.uid
|
||||
http.auth_methods['public'] = auth_method_public
|
||||
|
||||
def url_for(path, lang=None, keep_query=None):
|
||||
if request:
|
||||
path = urljoin(request.httprequest.path, path)
|
||||
def url_for(path_or_uri, lang=None, keep_query=None):
|
||||
location = path_or_uri.strip()
|
||||
url = urlparse.urlparse(location)
|
||||
if request and not url.netloc and not url.scheme:
|
||||
location = urlparse.urljoin(request.httprequest.path, location)
|
||||
langs = request.context.get('langs')
|
||||
if path[0] == '/' and len(langs) > 1:
|
||||
ps = path.split('/')
|
||||
if location[0] == '/' and (len(langs) > 1 or lang):
|
||||
ps = location.split('/')
|
||||
lang = lang or request.context.get('lang')
|
||||
if ps[1] in langs:
|
||||
ps[1] = lang
|
||||
else:
|
||||
ps.insert(1, lang)
|
||||
path = '/'.join(ps)
|
||||
location = '/'.join(ps)
|
||||
if keep_query:
|
||||
keep = []
|
||||
params = werkzeug.url_decode(request.httprequest.query_string)
|
||||
params_keys = tuple(params.keys())
|
||||
url = urlparse.urlparse(location)
|
||||
location = url.path
|
||||
params = werkzeug.url_decode(url.query)
|
||||
query_params = frozenset(werkzeug.url_decode(request.httprequest.query_string).keys())
|
||||
for kq in keep_query:
|
||||
keep += fnmatch.filter(params_keys, kq)
|
||||
if keep:
|
||||
params = dict([(k, params[k]) for k in keep])
|
||||
path += u'?%s' % werkzeug.urls.url_encode(params)
|
||||
for param in fnmatch.filter(query_params, kq):
|
||||
params[param] = request.params[param]
|
||||
params = werkzeug.urls.url_encode(params)
|
||||
if params:
|
||||
location += '?%s' % params
|
||||
|
||||
return path
|
||||
return location
|
||||
|
||||
def urlplus(url, params):
|
||||
if not params:
|
||||
|
@ -103,32 +109,28 @@ class website(osv.osv):
|
|||
|
||||
public_user = None
|
||||
|
||||
def get_public_user(self):
|
||||
def get_public_user(self, cr, uid, context=None):
|
||||
if not self.public_user:
|
||||
ref = request.registry['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'website', 'public_user')
|
||||
self.public_user = request.registry[ref[0]].browse(request.cr, openerp.SUPERUSER_ID, ref[1])
|
||||
uid = openerp.SUPERUSER_ID
|
||||
ref = self.pool['ir.model.data'].get_object_reference(cr, uid, 'website', 'public_user')
|
||||
self.public_user = self.pool[ref[0]].browse(cr, uid, ref[1])
|
||||
return self.public_user
|
||||
|
||||
def get_lang(self):
|
||||
website = request.registry['website'].get_current()
|
||||
|
||||
if hasattr(request, 'route_lang'):
|
||||
lang = request.route_lang
|
||||
else:
|
||||
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
|
||||
|
||||
if lang not in [lg.code for lg in website.language_ids]:
|
||||
lang = website.default_lang_id.code
|
||||
|
||||
return lang
|
||||
|
||||
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
|
||||
def preprocess_request(self, cr, uid, ids, request, context=None):
|
||||
def redirect(url):
|
||||
return werkzeug.utils.redirect(url_for(url))
|
||||
request.redirect = redirect
|
||||
|
||||
is_public_user = request.uid == self.get_public_user().id
|
||||
lang = self.get_lang()
|
||||
is_public_user = request.uid == self.get_public_user(cr, uid, context).id
|
||||
|
||||
# Select current language
|
||||
if hasattr(request, 'route_lang'):
|
||||
lang = request.route_lang
|
||||
else:
|
||||
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
|
||||
if lang not in [lg.code for lg in request.website.language_ids]:
|
||||
lang = request.website.default_lang_id.code
|
||||
|
||||
is_master_lang = lang == request.website.default_lang_id.code
|
||||
request.context.update({
|
||||
'lang': lang,
|
||||
|
@ -141,16 +143,15 @@ class website(osv.osv):
|
|||
'translatable': not is_public_user and not is_master_lang and request.multilang,
|
||||
})
|
||||
|
||||
def get_current(self):
|
||||
# WIP, currently hard coded
|
||||
return self.browse(request.cr, request.uid, 1)
|
||||
def render(self, cr, uid, ids, template, values=None, context=None):
|
||||
view = self.pool.get("ir.ui.view")
|
||||
IMD = self.pool.get("ir.model.data")
|
||||
user = self.pool.get("res.users")
|
||||
|
||||
def render(self, cr, uid, ids, template, values=None):
|
||||
view = request.registry.get("ir.ui.view")
|
||||
IMD = request.registry.get("ir.model.data")
|
||||
user = request.registry.get("res.users")
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
qweb_context = request.context.copy()
|
||||
qweb_context = context.copy()
|
||||
|
||||
if values:
|
||||
qweb_context.update(values)
|
||||
|
@ -164,7 +165,6 @@ class website(osv.osv):
|
|||
user_id=user.browse(cr, uid, uid),
|
||||
)
|
||||
|
||||
context = request.context.copy()
|
||||
context.update(
|
||||
inherit_branding=qweb_context.setdefault('editable', False),
|
||||
)
|
||||
|
@ -179,12 +179,11 @@ class website(osv.osv):
|
|||
try:
|
||||
view_ref = IMD.get_object_reference(cr, uid, module, xmlid)
|
||||
except ValueError:
|
||||
logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
|
||||
return self.render(cr, uid, ids, 'website.404', qweb_context)
|
||||
return self.error(cr, uid, 404, qweb_context, context=context)
|
||||
|
||||
if 'main_object' not in qweb_context:
|
||||
try:
|
||||
main_object = request.registry[view_ref[0]].browse(cr, uid, view_ref[1])
|
||||
main_object = self.pool[view_ref[0]].browse(cr, uid, view_ref[1])
|
||||
qweb_context['main_object'] = main_object
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -197,26 +196,25 @@ class website(osv.osv):
|
|||
logger.error(err)
|
||||
qweb_context['error'] = err[1]
|
||||
logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
|
||||
return self.render(cr, uid, ids, 'website.401', qweb_context)
|
||||
except (QWebException,), err:
|
||||
return self.error(cr, uid, 401, qweb_context, context=context)
|
||||
except Exception, e:
|
||||
qweb_context['template'] = getattr(e, 'qweb_template', '')
|
||||
node = getattr(e, 'qweb_node', None)
|
||||
qweb_context['node'] = node and node.toxml()
|
||||
qweb_context['expr'] = getattr(e, 'qweb_eval', '')
|
||||
qweb_context['traceback'] = traceback.format_exc()
|
||||
qweb_context['template'] = err.template
|
||||
qweb_context['message'] = err.message
|
||||
qweb_context['node'] = err.node and err.node.toxml()
|
||||
logger.error("Website Rendering Error.\n%(message)s\n%(node)s\n\n%(traceback)s" % qweb_context)
|
||||
return view.render(
|
||||
cr, uid,
|
||||
'website.500' if qweb_context['editable'] else 'website.404',
|
||||
qweb_context, context=context)
|
||||
except Exception:
|
||||
logger.exception("Website Rendering Error.")
|
||||
qweb_context['traceback'] = traceback.format_exc()
|
||||
return view.render(
|
||||
cr, uid,
|
||||
'website.500' if qweb_context['editable'] else 'website.404',
|
||||
qweb_context, context=context)
|
||||
logger.exception("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s" % qweb_context)
|
||||
return self.error(cr, uid, 500 if qweb_context['editable'] else 404,
|
||||
qweb_context, context=context)
|
||||
|
||||
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None):
|
||||
def error(self, cr, uid, code, qweb_context, context=None):
|
||||
View = request.registry['ir.ui.view']
|
||||
return werkzeug.wrappers.Response(
|
||||
View.render(cr, uid, 'website.%d' % code, qweb_context),
|
||||
status=code,
|
||||
content_type='text/html;charset=utf-8')
|
||||
|
||||
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
|
||||
# Compute Pager
|
||||
page_count = int(math.ceil(float(total) / step))
|
||||
|
||||
|
@ -264,6 +262,64 @@ class website(osv.osv):
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
def rule_is_enumerable(self, rule):
|
||||
""" Checks that it is possible to generate sensible GET queries for
|
||||
a given rule (if the endpoint matches its own requirements)
|
||||
|
||||
:type rule: werkzeug.routing.Rule
|
||||
:rtype: bool
|
||||
"""
|
||||
endpoint = rule.endpoint
|
||||
methods = rule.methods or ['GET']
|
||||
|
||||
return (
|
||||
'GET' in methods
|
||||
and endpoint.exposed == 'http'
|
||||
and endpoint.auth in ('none', 'public')
|
||||
and getattr(endpoint, 'cms', False)
|
||||
# ensure all converters on the rule are able to generate values for
|
||||
# themselves
|
||||
and all(hasattr(converter, 'generate')
|
||||
for converter in rule._converters.itervalues())
|
||||
) and self.endpoint_is_enumerable(rule)
|
||||
|
||||
def endpoint_is_enumerable(self, rule):
|
||||
""" Verifies that it's possible to generate a valid url for the rule's
|
||||
endpoint
|
||||
|
||||
:type rule: werkzeug.routing.Rule
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
# apparently the decorator package makes getargspec work correctly
|
||||
# on functions it decorates. That's not the case for
|
||||
# @functools.wraps, so hack around to get the original function
|
||||
# (and hope a single decorator was applied or we're hosed)
|
||||
# FIXME: this is going to blow up if we want/need to use multiple @route (with various configurations) on a method
|
||||
undecorated_func = rule.endpoint.func_closure[0].cell_contents
|
||||
|
||||
# If this is ever ported to py3, use signatures, it doesn't suck as much
|
||||
spec = inspect.getargspec(undecorated_func)
|
||||
|
||||
# if *args or **kwargs, just bail the fuck out, only dragons can
|
||||
# live there
|
||||
if spec.varargs or spec.keywords:
|
||||
return False
|
||||
|
||||
# remove all arguments with a default value from the list
|
||||
defaults_count = len(spec.defaults or []) # spec.defaults can be None
|
||||
# a[:-0] ~ a[:0] ~ [] -> replace defaults_count == 0 by None to get
|
||||
# a[:None] ~ a
|
||||
args = spec.args[:(-defaults_count or None)]
|
||||
|
||||
# params with defaults were removed, leftover allowed are:
|
||||
# * self (technically should be first-parameter-of-instance-method but whatever)
|
||||
# * any parameter mapping to a converter
|
||||
return all(
|
||||
(arg == 'self' or arg in rule._converters)
|
||||
for arg in args)
|
||||
|
||||
def list_pages(self, cr, uid, ids, context=None):
|
||||
""" Available pages in the website/CMS. This is mostly used for links
|
||||
generation and can be overridden by modules setting up new HTML
|
||||
|
@ -276,26 +332,33 @@ class website(osv.osv):
|
|||
of the same.
|
||||
:rtype: list({name: str, url: str})
|
||||
"""
|
||||
View = self.pool['ir.ui.view']
|
||||
views = View.search_read(cr, uid, [['page', '=', True]],
|
||||
fields=['name'], order='name', context=context)
|
||||
xids = View.get_external_id(cr, uid, [view['id'] for view in views], context=context)
|
||||
|
||||
return [
|
||||
{'name': view['name'], 'url': '/page/' + xids[view['id']]}
|
||||
for view in views
|
||||
if xids[view['id']]
|
||||
]
|
||||
router = request.httprequest.app.get_db_router(request.db)
|
||||
|
||||
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None):
|
||||
for rule in router.iter_rules():
|
||||
endpoint = rule.endpoint
|
||||
if not self.rule_is_enumerable(rule):
|
||||
continue
|
||||
|
||||
generated = map(dict, itertools.product(*(
|
||||
itertools.izip(itertools.repeat(name), converter.generate())
|
||||
for name, converter in rule._converters.iteritems()
|
||||
)))
|
||||
|
||||
for values in generated:
|
||||
# rule.build returns (domain_part, rel_url)
|
||||
url = rule.build(values, append_unknown=False)[1]
|
||||
yield {'name': url, 'url': url }
|
||||
|
||||
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None):
|
||||
step = step and int(step) or 10
|
||||
scope = scope and int(scope) or 5
|
||||
orderby = orderby or "name"
|
||||
|
||||
get_args = dict(request.httprequest.args or {})
|
||||
model_obj = request.registry[model]
|
||||
model_obj = self.pool[model]
|
||||
relation = model_obj._columns.get(column)._obj
|
||||
relation_obj = request.registry[relation]
|
||||
relation_obj = self.pool[relation]
|
||||
|
||||
get_args.setdefault('kanban', "")
|
||||
kanban = get_args.pop('kanban')
|
||||
|
@ -349,9 +412,9 @@ class website(osv.osv):
|
|||
}
|
||||
return request.website.render("website.kanban_contain", values)
|
||||
|
||||
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby):
|
||||
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby, context=None):
|
||||
html = ""
|
||||
model_obj = request.registry[model]
|
||||
model_obj = self.pool[model]
|
||||
domain = safe_eval(domain)
|
||||
step = int(step)
|
||||
offset = (int(page)-1) * step
|
||||
|
@ -361,6 +424,74 @@ class website(osv.osv):
|
|||
html += request.website.render(template, {'object_id': object_id})
|
||||
return html
|
||||
|
||||
def get_menu(self, cr, uid, ids, context=None):
|
||||
return self.pool['website.menu'].get_menu(cr, uid, ids[0], context=context)
|
||||
|
||||
class website_menu(osv.osv):
|
||||
_name = "website.menu"
|
||||
_description = "Website Menu"
|
||||
_columns = {
|
||||
'name': fields.char('Menu', size=64, required=True, translate=True),
|
||||
'url': fields.char('Url', required=True, translate=True),
|
||||
'new_window': fields.boolean('New Window'),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
# TODO: support multiwebsite once done for ir.ui.views
|
||||
'website_id': fields.many2one('website', 'Website'),
|
||||
'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"),
|
||||
'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'),
|
||||
'parent_left': fields.integer('Parent Left', select=True),
|
||||
'parent_right': fields.integer('Parent Right', select=True),
|
||||
}
|
||||
_defaults = {
|
||||
'url': '',
|
||||
'sequence': 0,
|
||||
}
|
||||
_parent_store = True
|
||||
_parent_order = 'sequence, name'
|
||||
_order = "parent_left"
|
||||
|
||||
def get_menu(self, cr, uid, website_id, context=None):
|
||||
root_domain = [('parent_id', '=', False)] # ('website_id', '=', website_id),
|
||||
menu_ids = self.search(cr, uid, root_domain, context=context)
|
||||
menu = self.browse(cr, uid, menu_ids, context=context)
|
||||
return menu[0]
|
||||
|
||||
def get_tree(self, cr, uid, website_id, context=None):
|
||||
def make_tree(node):
|
||||
menu_node = dict(
|
||||
id=node.id,
|
||||
name=node.name,
|
||||
url=node.url,
|
||||
new_window=node.new_window,
|
||||
sequence=node.sequence,
|
||||
parent_id=node.parent_id.id,
|
||||
children=[],
|
||||
)
|
||||
for child in node.child_id:
|
||||
menu_node['children'].append(make_tree(child))
|
||||
return menu_node
|
||||
menu = self.get_menu(cr, uid, website_id, context=context)
|
||||
return make_tree(menu)
|
||||
|
||||
def save(self, cr, uid, website_id, data, context=None):
|
||||
def replace_id(old_id, new_id):
|
||||
for menu in data['data']:
|
||||
if menu['id'] == old_id:
|
||||
menu['id'] = new_id
|
||||
if menu['parent_id'] == old_id:
|
||||
menu['parent_id'] = new_id
|
||||
to_delete = data['to_delete']
|
||||
if to_delete:
|
||||
self.unlink(cr, uid, to_delete, context=context)
|
||||
for menu in data['data']:
|
||||
mid = menu['id']
|
||||
if isinstance(mid, str):
|
||||
new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
|
||||
replace_id(mid, new_id)
|
||||
for menu in data['data']:
|
||||
self.write(cr, uid, [menu['id']], menu, context=context)
|
||||
return True
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
_inherit = "ir.attachment"
|
||||
def _website_url_get(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -401,6 +532,15 @@ class res_partner(osv.osv):
|
|||
}
|
||||
return urlplus('https://maps.google.be/maps' , params)
|
||||
|
||||
class res_company(osv.osv):
|
||||
_inherit = "res.company"
|
||||
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
|
||||
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id
|
||||
return partner and partner.google_map_img(zoom, width, height, context=context) or None
|
||||
def google_map_link(self, cr, uid, ids, zoom=8, context=None):
|
||||
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id
|
||||
return partner and partner.google_map_link(zoom, context=context) or None
|
||||
|
||||
class base_language_install(osv.osv):
|
||||
_inherit = "base.language.install"
|
||||
_columns = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_website_public,website,website.model_website,base.group_public,1,0,0,0
|
||||
access_website,website,website.model_website,base.group_user,1,0,0,0
|
||||
access_website_menu,access_website_menu,model_website_menu,,1,0,0,0
|
||||
access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1
|
||||
access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1
|
||||
access_website_qweb,access_website_qweb,model_website_qweb,,0,0,0,0
|
||||
|
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,429 @@
|
|||
/*
|
||||
* jQuery UI Nested Sortable
|
||||
* v 1.3.5 / 21 jun 2012
|
||||
* http://mjsarfatti.com/code/nestedSortable
|
||||
*
|
||||
* Depends on:
|
||||
* jquery.ui.sortable.js 1.8+
|
||||
*
|
||||
* Copyright (c) 2010-2012 Manuele J Sarfatti
|
||||
* Licensed under the MIT License
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
$.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
|
||||
|
||||
options: {
|
||||
tabSize: 20,
|
||||
disableNesting: 'mjs-nestedSortable-no-nesting',
|
||||
errorClass: 'mjs-nestedSortable-error',
|
||||
doNotClear: false,
|
||||
listType: 'ol',
|
||||
maxLevels: 0,
|
||||
protectRoot: false,
|
||||
rootID: null,
|
||||
rtl: false,
|
||||
isAllowed: function(item, parent) { return true; }
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
this.element.data('sortable', this.element.data('nestedSortable'));
|
||||
|
||||
if (!this.element.is(this.options.listType))
|
||||
throw new Error('nestedSortable: Please check the listType option is set to your actual list type');
|
||||
|
||||
return $.ui.sortable.prototype._create.apply(this, arguments);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.element
|
||||
.removeData("nestedSortable")
|
||||
.unbind(".nestedSortable");
|
||||
return $.ui.sortable.prototype.destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
_mouseDrag: function(event) {
|
||||
|
||||
//Compute the helpers position
|
||||
this.position = this._generatePosition(event);
|
||||
this.positionAbs = this._convertPositionTo("absolute");
|
||||
|
||||
if (!this.lastPositionAbs) {
|
||||
this.lastPositionAbs = this.positionAbs;
|
||||
}
|
||||
|
||||
var o = this.options;
|
||||
|
||||
//Do scrolling
|
||||
if(this.options.scroll) {
|
||||
var scrolled = false;
|
||||
if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
|
||||
|
||||
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
|
||||
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
|
||||
else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
|
||||
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
|
||||
|
||||
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
|
||||
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
|
||||
else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
|
||||
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
|
||||
|
||||
} else {
|
||||
|
||||
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
|
||||
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
||||
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
|
||||
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
||||
|
||||
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
|
||||
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
||||
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
|
||||
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
||||
|
||||
}
|
||||
|
||||
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
|
||||
$.ui.ddmanager.prepareOffsets(this, event);
|
||||
}
|
||||
|
||||
//Regenerate the absolute position used for position checks
|
||||
this.positionAbs = this._convertPositionTo("absolute");
|
||||
|
||||
// Find the top offset before rearrangement,
|
||||
var previousTopOffset = this.placeholder.offset().top;
|
||||
|
||||
//Set the helper position
|
||||
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
|
||||
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
|
||||
|
||||
//Rearrange
|
||||
for (var i = this.items.length - 1; i >= 0; i--) {
|
||||
|
||||
//Cache variables and intersection, continue if no intersection
|
||||
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
|
||||
if (!intersection) continue;
|
||||
|
||||
if(itemElement != this.currentItem[0] //cannot intersect with itself
|
||||
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
|
||||
&& !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
|
||||
&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
|
||||
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
|
||||
) {
|
||||
|
||||
$(itemElement).mouseenter();
|
||||
|
||||
this.direction = intersection == 1 ? "down" : "up";
|
||||
|
||||
if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
|
||||
$(itemElement).mouseleave();
|
||||
this._rearrange(event, item);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear emtpy ul's/ol's
|
||||
this._clearEmpty(itemElement);
|
||||
|
||||
this._trigger("change", event, this._uiHash());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var parentItem = (this.placeholder[0].parentNode.parentNode &&
|
||||
$(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length)
|
||||
? $(this.placeholder[0].parentNode.parentNode)
|
||||
: null,
|
||||
level = this._getLevel(this.placeholder),
|
||||
childLevels = this._getChildLevels(this.helper);
|
||||
|
||||
// To find the previous sibling in the list, keep backtracking until we hit a valid list item.
|
||||
var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
|
||||
if (previousItem != null) {
|
||||
while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) {
|
||||
if (previousItem[0].previousSibling) {
|
||||
previousItem = $(previousItem[0].previousSibling);
|
||||
} else {
|
||||
previousItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To find the next sibling in the list, keep stepping forward until we hit a valid list item.
|
||||
var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
|
||||
if (nextItem != null) {
|
||||
while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) {
|
||||
if (nextItem[0].nextSibling) {
|
||||
nextItem = $(nextItem[0].nextSibling);
|
||||
} else {
|
||||
nextItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newList = document.createElement(o.listType);
|
||||
|
||||
this.beyondMaxLevels = 0;
|
||||
|
||||
// If the item is moved to the left, send it to its parent's level unless there are siblings below it.
|
||||
if (parentItem != null && nextItem == null &&
|
||||
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) ||
|
||||
!o.rtl && (this.positionAbs.left < parentItem.offset().left))) {
|
||||
parentItem.after(this.placeholder[0]);
|
||||
this._clearEmpty(parentItem[0]);
|
||||
this._trigger("change", event, this._uiHash());
|
||||
}
|
||||
// If the item is below a sibling and is moved to the right, make it a child of that sibling.
|
||||
else if (previousItem != null &&
|
||||
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) ||
|
||||
!o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) {
|
||||
this._isAllowed(previousItem, level, level+childLevels+1);
|
||||
if (!previousItem.children(o.listType).length) {
|
||||
previousItem[0].appendChild(newList);
|
||||
}
|
||||
// If this item is being moved from the top, add it to the top of the list.
|
||||
if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
|
||||
previousItem.children(o.listType).prepend(this.placeholder);
|
||||
}
|
||||
// Otherwise, add it to the bottom of the list.
|
||||
else {
|
||||
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
|
||||
}
|
||||
this._trigger("change", event, this._uiHash());
|
||||
}
|
||||
else {
|
||||
this._isAllowed(parentItem, level, level+childLevels);
|
||||
}
|
||||
|
||||
//Post events to containers
|
||||
this._contactContainers(event);
|
||||
|
||||
//Interconnect with droppables
|
||||
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
|
||||
|
||||
//Call callbacks
|
||||
this._trigger('sort', event, this._uiHash());
|
||||
|
||||
this.lastPositionAbs = this.positionAbs;
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
_mouseStop: function(event, noPropagation) {
|
||||
|
||||
// If the item is in a position not allowed, send it back
|
||||
if (this.beyondMaxLevels) {
|
||||
|
||||
this.placeholder.removeClass(this.options.errorClass);
|
||||
|
||||
if (this.domPosition.prev) {
|
||||
$(this.domPosition.prev).after(this.placeholder);
|
||||
} else {
|
||||
$(this.domPosition.parent).prepend(this.placeholder);
|
||||
}
|
||||
|
||||
this._trigger("revert", event, this._uiHash());
|
||||
|
||||
}
|
||||
|
||||
// Clean last empty ul/ol
|
||||
for (var i = this.items.length - 1; i >= 0; i--) {
|
||||
var item = this.items[i].item[0];
|
||||
this._clearEmpty(item);
|
||||
}
|
||||
|
||||
$.ui.sortable.prototype._mouseStop.apply(this, arguments);
|
||||
|
||||
},
|
||||
|
||||
serialize: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
items = this._getItemsAsjQuery(o && o.connected),
|
||||
str = [];
|
||||
|
||||
$(items).each(function() {
|
||||
var res = ($(o.item || this).attr(o.attribute || 'id') || '')
|
||||
.match(o.expression || (/(.+)[-=_](.+)/)),
|
||||
pid = ($(o.item || this).parent(o.listType)
|
||||
.parent(o.items)
|
||||
.attr(o.attribute || 'id') || '')
|
||||
.match(o.expression || (/(.+)[-=_](.+)/));
|
||||
|
||||
if (res) {
|
||||
str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']')
|
||||
+ '='
|
||||
+ (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
|
||||
}
|
||||
});
|
||||
|
||||
if(!str.length && o.key) {
|
||||
str.push(o.key + '=');
|
||||
}
|
||||
|
||||
return str.join('&');
|
||||
|
||||
},
|
||||
|
||||
toHierarchy: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
sDepth = o.startDepthCount || 0,
|
||||
ret = [];
|
||||
|
||||
$(this.element).children(o.items).each(function () {
|
||||
var level = _recursiveItems(this);
|
||||
ret.push(level);
|
||||
});
|
||||
|
||||
return ret;
|
||||
|
||||
function _recursiveItems(item) {
|
||||
var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
||||
if (id) {
|
||||
var currentItem = {"id" : id[2]};
|
||||
if ($(item).children(o.listType).children(o.items).length > 0) {
|
||||
currentItem.children = [];
|
||||
$(item).children(o.listType).children(o.items).each(function() {
|
||||
var level = _recursiveItems(this);
|
||||
currentItem.children.push(level);
|
||||
});
|
||||
}
|
||||
return currentItem;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toArray: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
sDepth = o.startDepthCount || 0,
|
||||
ret = [],
|
||||
left = 2;
|
||||
|
||||
ret.push({
|
||||
"item_id": o.rootID,
|
||||
"parent_id": 'none',
|
||||
"depth": sDepth,
|
||||
"left": '1',
|
||||
"right": ($(o.items, this.element).length + 1) * 2
|
||||
});
|
||||
|
||||
$(this.element).children(o.items).each(function () {
|
||||
left = _recursiveArray(this, sDepth + 1, left);
|
||||
});
|
||||
|
||||
ret = ret.sort(function(a,b){ return (a.left - b.left); });
|
||||
|
||||
return ret;
|
||||
|
||||
function _recursiveArray(item, depth, left) {
|
||||
|
||||
var right = left + 1,
|
||||
id,
|
||||
pid;
|
||||
|
||||
if ($(item).children(o.listType).children(o.items).length > 0) {
|
||||
depth ++;
|
||||
$(item).children(o.listType).children(o.items).each(function () {
|
||||
right = _recursiveArray($(this), depth, right);
|
||||
});
|
||||
depth --;
|
||||
}
|
||||
|
||||
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
|
||||
|
||||
if (depth === sDepth + 1) {
|
||||
pid = o.rootID;
|
||||
} else {
|
||||
var parentItem = ($(item).parent(o.listType)
|
||||
.parent(o.items)
|
||||
.attr(o.attribute || 'id'))
|
||||
.match(o.expression || (/(.+)[-=_](.+)/));
|
||||
pid = parentItem[2];
|
||||
}
|
||||
|
||||
if (id) {
|
||||
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
|
||||
}
|
||||
|
||||
left = right + 1;
|
||||
return left;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_clearEmpty: function(item) {
|
||||
|
||||
var emptyList = $(item).children(this.options.listType);
|
||||
if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) {
|
||||
emptyList.remove();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_getLevel: function(item) {
|
||||
|
||||
var level = 1;
|
||||
|
||||
if (this.options.listType) {
|
||||
var list = item.closest(this.options.listType);
|
||||
while (list && list.length > 0 &&
|
||||
!list.is('.ui-sortable')) {
|
||||
level++;
|
||||
list = list.parent().closest(this.options.listType);
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
},
|
||||
|
||||
_getChildLevels: function(parent, depth) {
|
||||
var self = this,
|
||||
o = this.options,
|
||||
result = 0;
|
||||
depth = depth || 0;
|
||||
|
||||
$(parent).children(o.listType).children(o.items).each(function (index, child) {
|
||||
result = Math.max(self._getChildLevels(child, depth + 1), result);
|
||||
});
|
||||
|
||||
return depth ? result + 1 : result;
|
||||
},
|
||||
|
||||
_isAllowed: function(parentItem, level, levels) {
|
||||
var o = this.options,
|
||||
isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false,
|
||||
maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list
|
||||
|
||||
// Is the root protected?
|
||||
// Are we trying to nest under a no-nest?
|
||||
// Are we nesting too deep?
|
||||
if (!o.isAllowed(this.currentItem, parentItem) ||
|
||||
parentItem && parentItem.hasClass(o.disableNesting) ||
|
||||
o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
if (maxLevels < levels && maxLevels != 0) {
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.beyondMaxLevels = 1;
|
||||
}
|
||||
} else {
|
||||
if (maxLevels < levels && maxLevels != 0) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.placeholder.removeClass(o.errorClass);
|
||||
this.beyondMaxLevels = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
$.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options);
|
||||
})(jQuery);
|
|
@ -1,20 +0,0 @@
|
|||
Copyright (c) 2013 Mark Dalgleish
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,308 +0,0 @@
|
|||
[![Build Status](https://secure.travis-ci.org/markdalgleish/stellar.js.png)](http://travis-ci.org/markdalgleish/stellar.js)
|
||||
|
||||
# Stellar.js
|
||||
|
||||
### Parallax scrolling made easy
|
||||
|
||||
Full guide and demonstrations available at the [official Stellar.js project page](http://markdalgleish.com/projects/stellar.js/).
|
||||
|
||||
## Download
|
||||
|
||||
Get the [development](https://raw.github.com/markdalgleish/stellar.js/master/jquery.stellar.js) or [production](https://raw.github.com/markdalgleish/stellar.js/master/jquery.stellar.min.js) version, or use a [package manager](https://github.com/markdalgleish/stellar.js#package-managers).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Stellar.js is a jQuery plugin that provides parallax scrolling effects to any scrolling element. The first step is to run `.stellar()` against the element:
|
||||
|
||||
``` js
|
||||
// For example:
|
||||
$(window).stellar();
|
||||
// or:
|
||||
$('#main').stellar();
|
||||
```
|
||||
|
||||
If you're running Stellar.js on 'window', you can use the shorthand:
|
||||
|
||||
``` js
|
||||
$.stellar();
|
||||
```
|
||||
|
||||
This will look for any parallax backgrounds or elements within the specified element and reposition them when the element scrolls.
|
||||
|
||||
## Mobile Support
|
||||
|
||||
Support in Mobile WebKit browsers requires a touch scrolling library, and a slightly tweaked configuration. For a full walkthrough on how to implement this correctly, read my blog post ["Mobile Parallax with Stellar.js"](http://markdalgleish.com/2012/10/mobile-parallax-with-stellar-js).
|
||||
|
||||
Please note that parallax backgrounds are not recommended in Mobile WebKit due to performance constraints. Instead, use parallax elements with static backgrounds.
|
||||
|
||||
## Parallax Elements
|
||||
|
||||
If you want elements to scroll at a different speed, add the following attribute to any element with a CSS position of absolute, relative or fixed:
|
||||
|
||||
``` html
|
||||
<div data-stellar-ratio="2">
|
||||
```
|
||||
|
||||
The ratio is relative to the natural scroll speed, so a ratio of 0.5 would cause the element to scroll at half-speed, a ratio of 1 would have no effect, and a ratio of 2 would cause the element to scroll at twice the speed. If a ratio lower than 1 is causing the element to appear jittery, try setting its CSS position to fixed.
|
||||
|
||||
In order for Stellar.js to perform its calculations correctly, all parallax elements must have their dimensions specified in pixels for the axis/axes being used for parallax effects. For example, all parallax elements for a vertical site must have a pixel height specified. If your design prohibits the use of pixels, try using the ['responsive' option](#configuring-everything).
|
||||
|
||||
## Parallax Backgrounds
|
||||
|
||||
If you want an element's background image to reposition on scroll, simply add the following attribute:
|
||||
|
||||
``` html
|
||||
<div data-stellar-background-ratio="0.5">
|
||||
```
|
||||
|
||||
As with parallax elements, the ratio is relative to the natural scroll speed. For ratios lower than 1, to avoid jittery scroll performance, set the element's CSS 'background-attachment' to fixed.
|
||||
|
||||
## Configuring Offsets
|
||||
|
||||
Stellar.js' most powerful feature is the way it aligns elements.
|
||||
|
||||
All elements will return to their original positioning when their offset parent meets the edge of the screen—plus or minus your own optional offset. This allows you to create intricate parallax patterns very easily.
|
||||
|
||||
Confused? [See how offsets are used on the Stellar.js home page.](http://markdalgleish.com/projects/stellar.js/#show-offsets)
|
||||
|
||||
To modify the offsets for all elements at once, pass in the options:
|
||||
|
||||
``` js
|
||||
$.stellar({
|
||||
horizontalOffset: 40,
|
||||
verticalOffset: 150
|
||||
});
|
||||
```
|
||||
|
||||
You can also modify the offsets on a per-element basis using the following data attributes:
|
||||
|
||||
``` html
|
||||
<div data-stellar-ratio="2"
|
||||
data-stellar-horizontal-offset="40"
|
||||
data-stellar-vertical-offset="150">
|
||||
```
|
||||
|
||||
## Configuring Offset Parents
|
||||
|
||||
By default, offsets are relative to the element's offset parent. This mirrors the way an absolutely positioned element behaves when nested inside an element with a relative position.
|
||||
|
||||
As with regular CSS, the closest parent element with a position of relative or absolute is the offset parent.
|
||||
|
||||
To override this and force the offset parent to be another element higher up the DOM, use the following data attribute:
|
||||
|
||||
``` html
|
||||
<div data-stellar-offset-parent="true">
|
||||
```
|
||||
|
||||
The offset parent can also have its own offsets:
|
||||
|
||||
``` html
|
||||
<div data-stellar-offset-parent="true"
|
||||
data-stellar-horizontal-offset="40"
|
||||
data-stellar-vertical-offset="150">
|
||||
```
|
||||
|
||||
Similar to CSS, the rules take precedence from element, to offset parent, to JavaScript options.
|
||||
|
||||
Confused? [See how offset parents are used on the Stellar.js home page.](http://markdalgleish.com/projects/stellar.js/#show-offset-parents)
|
||||
|
||||
Still confused? [See what it looks like with its default offset parents.](http://markdalgleish.com/projects/stellar.js/#show-offset-parents-default) Notice how the alignment happens on a per-letter basis? That's because each letter's containing div is its default offset parent.
|
||||
|
||||
By specifying the h2 element as the offset parent, we can ensure that the alignment of all the stars in a heading is based on the h2 and not the div further down the DOM tree.
|
||||
|
||||
## Configuring Scroll Positioning
|
||||
|
||||
You can define what it means for an element to 'scroll'. Whether it's the element's scroll position that's changing, its margins or its CSS3 'transform' position, you can define it using the 'scrollProperty' option:
|
||||
|
||||
``` js
|
||||
$('#gallery').stellar({
|
||||
scrollProperty: 'transform'
|
||||
});
|
||||
```
|
||||
|
||||
This option is what allows you to run [Stellar.js on iOS](http://markdalgleish.com/projects/stellar.js/demos/ios.html).
|
||||
|
||||
You can even define how the elements are repositioned, whether it's through standard top and left properties or using CSS3 transforms:
|
||||
|
||||
``` js
|
||||
$('#gallery').stellar({
|
||||
positionProperty: 'transform'
|
||||
});
|
||||
```
|
||||
|
||||
Don't have the level of control you need? Write a plugin!
|
||||
|
||||
Otherwise, you're ready to get started!
|
||||
|
||||
## Configuring Everything
|
||||
|
||||
Below you will find a complete list of options and matching default values:
|
||||
|
||||
``` js
|
||||
$.stellar({
|
||||
// Set scrolling to be in either one or both directions
|
||||
horizontalScrolling: true,
|
||||
verticalScrolling: true,
|
||||
|
||||
// Set the global alignment offsets
|
||||
horizontalOffset: 0,
|
||||
verticalOffset: 0,
|
||||
|
||||
// Refreshes parallax content on window load and resize
|
||||
responsive: false,
|
||||
|
||||
// Select which property is used to calculate scroll.
|
||||
// Choose 'scroll', 'position', 'margin' or 'transform',
|
||||
// or write your own 'scrollProperty' plugin.
|
||||
scrollProperty: 'scroll',
|
||||
|
||||
// Select which property is used to position elements.
|
||||
// Choose between 'position' or 'transform',
|
||||
// or write your own 'positionProperty' plugin.
|
||||
positionProperty: 'position',
|
||||
|
||||
// Enable or disable the two types of parallax
|
||||
parallaxBackgrounds: true,
|
||||
parallaxElements: true,
|
||||
|
||||
// Hide parallax elements that move outside the viewport
|
||||
hideDistantElements: true,
|
||||
|
||||
// Customise how elements are shown and hidden
|
||||
hideElement: function($elem) { $elem.hide(); },
|
||||
showElement: function($elem) { $elem.show(); }
|
||||
});
|
||||
```
|
||||
|
||||
## Writing a Scroll Property Plugin
|
||||
|
||||
Out of the box, Stellar.js supports the following scroll properties:
|
||||
'scroll', 'position', 'margin' and 'transform'.
|
||||
|
||||
If your method for creating a scrolling interface isn't covered by one of these, you can write your own. For example, if 'margin' didn't exist yet you could write it like so:
|
||||
|
||||
``` js
|
||||
$.stellar.scrollProperty.margin = {
|
||||
getLeft: function($element) {
|
||||
return parseInt($element.css('margin-left'), 10) * -1;
|
||||
},
|
||||
getTop: function($element) {
|
||||
return parseInt($element.css('margin-top'), 10) * -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can specify this scroll property in Stellar.js' configuration.
|
||||
|
||||
``` js
|
||||
$.stellar({
|
||||
scrollProperty: 'margin'
|
||||
});
|
||||
```
|
||||
|
||||
## Writing a Position Property Plugin
|
||||
|
||||
Stellar.js has two methods for positioning elements built in: 'position' for modifying its top and left properties, and 'transform' for using CSS3 transforms.
|
||||
|
||||
If you need more control over how elements are positioned, you can write your own setter functions. For example, if 'position' didn't exist yet, it could be written as a plugin like this:
|
||||
|
||||
``` js
|
||||
$.stellar.positionProperty.position = {
|
||||
setTop: function($element, newTop, originalTop) {
|
||||
$element.css('top', newTop);
|
||||
},
|
||||
setLeft: function($element, newLeft, originalLeft) {
|
||||
$element.css('left', newLeft);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can specify this position property in Stellar.js' configuration.
|
||||
|
||||
``` js
|
||||
$.stellar({
|
||||
positionProperty: 'position'
|
||||
});
|
||||
```
|
||||
|
||||
If, for technical reasons, you need to set both properties at once, you can define a single 'setPosition' function:
|
||||
|
||||
``` js
|
||||
$.stellar.positionProperty.foobar = {
|
||||
setPosition: function($element, newLeft, originalLeft, newTop, originalTop) {
|
||||
$element.css('transform', 'translate3d(' +
|
||||
(newLeft - originalLeft) + 'px, ' +
|
||||
(newTop - originalTop) + 'px, ' +
|
||||
'0)');
|
||||
}
|
||||
}
|
||||
|
||||
$.stellar({
|
||||
positionProperty: 'foobar'
|
||||
});
|
||||
```
|
||||
|
||||
## Package Managers
|
||||
|
||||
Stellar.js can be installed with [Bower](http://twitter.github.com/bower/):
|
||||
|
||||
``` bash
|
||||
$ bower install jquery.stellar
|
||||
```
|
||||
|
||||
## Sites Using Stellar.js
|
||||
|
||||
* [National Geographic - Alien Deep Interactive](http://channel.nationalgeographic.com/channel/alien-deep/interactives/alien-deep-interactive)
|
||||
* [François Hollande](http://www.parti-socialiste.fr/latimelineduchangement)
|
||||
* [Brabus Private Aviation](http://www.brabus-aviation.com/)
|
||||
* [Mary and Frankie's Wedding](http://www.maryandfrankiewedding.com/)
|
||||
* [IT Support London](http://www.itsupportlondon.com)
|
||||
* [Ashford University](http://bright.ashford.edu)
|
||||
* [Clif Adventures](http://www.clifbar.com/adventures)
|
||||
* [Mindster](http://www.mindster.org)
|
||||
* [WS Interactive](http://www.ws-interactive.fr/methode)
|
||||
* [Moire Mag - Untitled](http://www.moiremag.net/untitled)
|
||||
* [Carnival of Courage](http://www.carnivalofcourage.com.au)
|
||||
* [Ian Poulter](http://www.ianpoulter.com)
|
||||
* [360 Strategy Group](http://360strategygroup.com)
|
||||
* [Code, Love and Boards](http://codeloveandboards.com/)
|
||||
|
||||
I'm sure there are heaps more. [Let me know if you'd like me to feature your site here.](http://twitter.com/markdalgleish)
|
||||
|
||||
## How to Build
|
||||
|
||||
Stellar.js uses [Node.js](nodejs.org), [Grunt](http://gruntjs.com) and [PhantomJS](http://phantomjs.org/).
|
||||
|
||||
Once you've got Node and PhantomJS set up, install the dependencies:
|
||||
|
||||
`$ npm install`
|
||||
|
||||
To lint, test and minify the project, simply run the following command:
|
||||
|
||||
`$ grunt`
|
||||
|
||||
Each of the build steps are also available individually.
|
||||
|
||||
`$ grunt test` to test the code using QUnit and PhantomJS:
|
||||
|
||||
`$ grunt lint` to validate the code using JSHint.
|
||||
|
||||
`$ grunt watch` to continuously lint and test the code while developing.
|
||||
|
||||
## Contributing to Stellar.js
|
||||
|
||||
Ensure that you successfully test and build the project with `$ grunt` before committing.
|
||||
|
||||
Make sure that all plugin changes are made in `src/jquery.stellar.js` (`/jquery.stellar.js` and `/jquery.stellar.min.js` are generated by Grunt).
|
||||
|
||||
If you want to contribute in a way that changes the API, please file an issue before submitting a pull request so we can discuss how to appropriately integrate your ideas.
|
||||
|
||||
## Questions?
|
||||
|
||||
Contact me on GitHub or Twitter: [@markdalgleish](http://twitter.com/markdalgleish)
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2013, Mark Dalgleish
|
||||
This content is released under the MIT license
|
||||
http://markdalgleish.mit-license.org
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "jquery.stellar",
|
||||
"version": "0.6.2",
|
||||
"main": ["./jquery.stellar.js"],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.4.3"
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*global module:false*/
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: '<json:package.json>',
|
||||
meta: {
|
||||
banner: '/*!\n' +
|
||||
' * <%= pkg.title || pkg.name %> v<%= pkg.version %>\n' +
|
||||
' * <%= pkg.homepage %>\n' +
|
||||
' * \n' +
|
||||
' * Copyright <%= grunt.template.today("yyyy") %>, <%= pkg.author.name %>\n' +
|
||||
' * This content is released under the <%= _.pluck(pkg.licenses, "type").join(", ") %> license<%= pkg.licenses.length === 1 ? "" : "s" %>\n' +
|
||||
' * <%= _.pluck(pkg.licenses, "url").join(", ") %>\n' +
|
||||
' */',
|
||||
microbanner: '/*! <%= pkg.title || pkg.name %> v<%= pkg.version %> | Copyright <%= grunt.template.today("yyyy") %>, <%= pkg.author.name %> | <%= pkg.homepage %> | <%= _.pluck(pkg.licenses, "url").join(", ") %> */'
|
||||
},
|
||||
lint: {
|
||||
files: ['grunt.js', 'test/**/*.js', 'src/**/*.js']
|
||||
},
|
||||
server: {
|
||||
port: 8573
|
||||
},
|
||||
qunit: {
|
||||
urls: ['1.4.3', '1.10.1', '2.0.2'].map(function(version) {
|
||||
return 'http://localhost:<%= server.port %>/test/jquery.stellar.html?jquery=' + version;
|
||||
})
|
||||
},
|
||||
concat: {
|
||||
dist: {
|
||||
src: ['<banner:meta.banner>', '<file_strip_banner:src/<%= pkg.name %>.js>'],
|
||||
dest: '<%= pkg.name %>.js'
|
||||
}
|
||||
},
|
||||
min: {
|
||||
dist: {
|
||||
src: ['<banner:meta.microbanner>', '<config:concat.dist.dest>'],
|
||||
dest: '<%= pkg.name %>.min.js'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
files: '<config:lint.files>',
|
||||
tasks: 'server lint qunit'
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
evil: true,
|
||||
curly: false,
|
||||
eqeqeq: true,
|
||||
immed: true,
|
||||
latedef: true,
|
||||
newcap: true,
|
||||
noarg: true,
|
||||
sub: true,
|
||||
undef: true,
|
||||
boss: true,
|
||||
browser: true
|
||||
},
|
||||
globals: {
|
||||
jQuery: true
|
||||
}
|
||||
},
|
||||
uglify: {}
|
||||
});
|
||||
|
||||
// Default task.
|
||||
grunt.registerTask('default', 'server lint qunit concat min');
|
||||
|
||||
grunt.registerTask('test', 'server lint qunit');
|
||||
|
||||
};
|
|
@ -1,660 +0,0 @@
|
|||
/*!
|
||||
* Stellar.js v0.6.2
|
||||
* http://markdalgleish.com/projects/stellar.js
|
||||
*
|
||||
* Copyright 2013, Mark Dalgleish
|
||||
* This content is released under the MIT license
|
||||
* http://markdalgleish.mit-license.org
|
||||
*/
|
||||
|
||||
;(function($, window, document, undefined) {
|
||||
|
||||
var pluginName = 'stellar',
|
||||
defaults = {
|
||||
scrollProperty: 'scroll',
|
||||
positionProperty: 'position',
|
||||
horizontalScrolling: true,
|
||||
verticalScrolling: true,
|
||||
horizontalOffset: 0,
|
||||
verticalOffset: 0,
|
||||
responsive: false,
|
||||
parallaxBackgrounds: true,
|
||||
parallaxElements: true,
|
||||
hideDistantElements: true,
|
||||
hideElement: function($elem) { $elem.hide(); },
|
||||
showElement: function($elem) { $elem.show(); }
|
||||
},
|
||||
|
||||
scrollProperty = {
|
||||
scroll: {
|
||||
getLeft: function($elem) { return $elem.scrollLeft(); },
|
||||
setLeft: function($elem, val) { $elem.scrollLeft(val); },
|
||||
|
||||
getTop: function($elem) { return $elem.scrollTop(); },
|
||||
setTop: function($elem, val) { $elem.scrollTop(val); }
|
||||
},
|
||||
position: {
|
||||
getLeft: function($elem) { return parseInt($elem.css('left'), 10) * -1; },
|
||||
getTop: function($elem) { return parseInt($elem.css('top'), 10) * -1; }
|
||||
},
|
||||
margin: {
|
||||
getLeft: function($elem) { return parseInt($elem.css('margin-left'), 10) * -1; },
|
||||
getTop: function($elem) { return parseInt($elem.css('margin-top'), 10) * -1; }
|
||||
},
|
||||
transform: {
|
||||
getLeft: function($elem) {
|
||||
var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
|
||||
return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[4], 10) * -1 : 0);
|
||||
},
|
||||
getTop: function($elem) {
|
||||
var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
|
||||
return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[5], 10) * -1 : 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
positionProperty = {
|
||||
position: {
|
||||
setLeft: function($elem, left) { $elem.css('left', left); },
|
||||
setTop: function($elem, top) { $elem.css('top', top); }
|
||||
},
|
||||
transform: {
|
||||
setPosition: function($elem, left, startingLeft, top, startingTop) {
|
||||
$elem[0].style[prefixedTransform] = 'translate3d(' + (left - startingLeft) + 'px, ' + (top - startingTop) + 'px, 0)';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Returns a function which adds a vendor prefix to any CSS property name
|
||||
vendorPrefix = (function() {
|
||||
var prefixes = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
|
||||
style = $('script')[0].style,
|
||||
prefix = '',
|
||||
prop;
|
||||
|
||||
for (prop in style) {
|
||||
if (prefixes.test(prop)) {
|
||||
prefix = prop.match(prefixes)[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ('WebkitOpacity' in style) { prefix = 'Webkit'; }
|
||||
if ('KhtmlOpacity' in style) { prefix = 'Khtml'; }
|
||||
|
||||
return function(property) {
|
||||
return prefix + (prefix.length > 0 ? property.charAt(0).toUpperCase() + property.slice(1) : property);
|
||||
};
|
||||
}()),
|
||||
|
||||
prefixedTransform = vendorPrefix('transform'),
|
||||
|
||||
supportsBackgroundPositionXY = $('<div />', { style: 'background:#fff' }).css('background-position-x') !== undefined,
|
||||
|
||||
setBackgroundPosition = (supportsBackgroundPositionXY ?
|
||||
function($elem, x, y) {
|
||||
$elem.css({
|
||||
'background-position-x': x,
|
||||
'background-position-y': y
|
||||
});
|
||||
} :
|
||||
function($elem, x, y) {
|
||||
$elem.css('background-position', x + ' ' + y);
|
||||
}
|
||||
),
|
||||
|
||||
getBackgroundPosition = (supportsBackgroundPositionXY ?
|
||||
function($elem) {
|
||||
return [
|
||||
$elem.css('background-position-x'),
|
||||
$elem.css('background-position-y')
|
||||
];
|
||||
} :
|
||||
function($elem) {
|
||||
return $elem.css('background-position').split(' ');
|
||||
}
|
||||
),
|
||||
|
||||
requestAnimFrame = (
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback) {
|
||||
setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
);
|
||||
|
||||
function Plugin(element, options) {
|
||||
this.element = element;
|
||||
this.options = $.extend({}, defaults, options);
|
||||
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
Plugin.prototype = {
|
||||
init: function() {
|
||||
this.options.name = pluginName + '_' + Math.floor(Math.random() * 1e9);
|
||||
|
||||
this._defineElements();
|
||||
this._defineGetters();
|
||||
this._defineSetters();
|
||||
this._handleWindowLoadAndResize();
|
||||
this._detectViewport();
|
||||
|
||||
this.refresh({ firstLoad: true });
|
||||
|
||||
if (this.options.scrollProperty === 'scroll') {
|
||||
this._handleScrollEvent();
|
||||
} else {
|
||||
this._startAnimationLoop();
|
||||
}
|
||||
},
|
||||
_defineElements: function() {
|
||||
if (this.element === document.body) this.element = window;
|
||||
this.$scrollElement = $(this.element);
|
||||
this.$element = (this.element === window ? $('body') : this.$scrollElement);
|
||||
this.$viewportElement = (this.options.viewportElement !== undefined ? $(this.options.viewportElement) : (this.$scrollElement[0] === window || this.options.scrollProperty === 'scroll' ? this.$scrollElement : this.$scrollElement.parent()) );
|
||||
},
|
||||
_defineGetters: function() {
|
||||
var self = this,
|
||||
scrollPropertyAdapter = scrollProperty[self.options.scrollProperty];
|
||||
|
||||
this._getScrollLeft = function() {
|
||||
return scrollPropertyAdapter.getLeft(self.$scrollElement);
|
||||
};
|
||||
|
||||
this._getScrollTop = function() {
|
||||
return scrollPropertyAdapter.getTop(self.$scrollElement);
|
||||
};
|
||||
},
|
||||
_defineSetters: function() {
|
||||
var self = this,
|
||||
scrollPropertyAdapter = scrollProperty[self.options.scrollProperty],
|
||||
positionPropertyAdapter = positionProperty[self.options.positionProperty],
|
||||
setScrollLeft = scrollPropertyAdapter.setLeft,
|
||||
setScrollTop = scrollPropertyAdapter.setTop;
|
||||
|
||||
this._setScrollLeft = (typeof setScrollLeft === 'function' ? function(val) {
|
||||
setScrollLeft(self.$scrollElement, val);
|
||||
} : $.noop);
|
||||
|
||||
this._setScrollTop = (typeof setScrollTop === 'function' ? function(val) {
|
||||
setScrollTop(self.$scrollElement, val);
|
||||
} : $.noop);
|
||||
|
||||
this._setPosition = positionPropertyAdapter.setPosition ||
|
||||
function($elem, left, startingLeft, top, startingTop) {
|
||||
if (self.options.horizontalScrolling) {
|
||||
positionPropertyAdapter.setLeft($elem, left, startingLeft);
|
||||
}
|
||||
|
||||
if (self.options.verticalScrolling) {
|
||||
positionPropertyAdapter.setTop($elem, top, startingTop);
|
||||
}
|
||||
};
|
||||
},
|
||||
_handleWindowLoadAndResize: function() {
|
||||
var self = this,
|
||||
$window = $(window);
|
||||
|
||||
if (self.options.responsive) {
|
||||
$window.bind('load.' + this.name, function() {
|
||||
self.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
$window.bind('resize.' + this.name, function() {
|
||||
self._detectViewport();
|
||||
|
||||
if (self.options.responsive) {
|
||||
self.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(options) {
|
||||
var self = this,
|
||||
oldLeft = self._getScrollLeft(),
|
||||
oldTop = self._getScrollTop();
|
||||
|
||||
if (!options || !options.firstLoad) {
|
||||
this._reset();
|
||||
}
|
||||
|
||||
this._setScrollLeft(0);
|
||||
this._setScrollTop(0);
|
||||
|
||||
this._setOffsets();
|
||||
this._findParticles();
|
||||
this._findBackgrounds();
|
||||
|
||||
// Fix for WebKit background rendering bug
|
||||
if (options && options.firstLoad && /WebKit/.test(navigator.userAgent)) {
|
||||
$(window).load(function() {
|
||||
var oldLeft = self._getScrollLeft(),
|
||||
oldTop = self._getScrollTop();
|
||||
|
||||
self._setScrollLeft(oldLeft + 1);
|
||||
self._setScrollTop(oldTop + 1);
|
||||
|
||||
self._setScrollLeft(oldLeft);
|
||||
self._setScrollTop(oldTop);
|
||||
});
|
||||
}
|
||||
|
||||
this._setScrollLeft(oldLeft);
|
||||
this._setScrollTop(oldTop);
|
||||
},
|
||||
_detectViewport: function() {
|
||||
var viewportOffsets = this.$viewportElement.offset(),
|
||||
hasOffsets = viewportOffsets !== null && viewportOffsets !== undefined;
|
||||
|
||||
this.viewportWidth = this.$viewportElement.width();
|
||||
this.viewportHeight = this.$viewportElement.height();
|
||||
|
||||
this.viewportOffsetTop = (hasOffsets ? viewportOffsets.top : 0);
|
||||
this.viewportOffsetLeft = (hasOffsets ? viewportOffsets.left : 0);
|
||||
},
|
||||
_findParticles: function() {
|
||||
var self = this,
|
||||
scrollLeft = this._getScrollLeft(),
|
||||
scrollTop = this._getScrollTop();
|
||||
|
||||
if (this.particles !== undefined) {
|
||||
for (var i = this.particles.length - 1; i >= 0; i--) {
|
||||
this.particles[i].$element.data('stellar-elementIsActive', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
this.particles = [];
|
||||
|
||||
if (!this.options.parallaxElements) return;
|
||||
|
||||
this.$element.find('[data-stellar-ratio]').each(function(i) {
|
||||
var $this = $(this),
|
||||
horizontalOffset,
|
||||
verticalOffset,
|
||||
positionLeft,
|
||||
positionTop,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
$offsetParent,
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
parentOffsetLeft = 0,
|
||||
parentOffsetTop = 0,
|
||||
tempParentOffsetLeft = 0,
|
||||
tempParentOffsetTop = 0;
|
||||
|
||||
// Ensure this element isn't already part of another scrolling element
|
||||
if (!$this.data('stellar-elementIsActive')) {
|
||||
$this.data('stellar-elementIsActive', this);
|
||||
} else if ($this.data('stellar-elementIsActive') !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.options.showElement($this);
|
||||
|
||||
// Save/restore the original top and left CSS values in case we refresh the particles or destroy the instance
|
||||
if (!$this.data('stellar-startingLeft')) {
|
||||
$this.data('stellar-startingLeft', $this.css('left'));
|
||||
$this.data('stellar-startingTop', $this.css('top'));
|
||||
} else {
|
||||
$this.css('left', $this.data('stellar-startingLeft'));
|
||||
$this.css('top', $this.data('stellar-startingTop'));
|
||||
}
|
||||
|
||||
positionLeft = $this.position().left;
|
||||
positionTop = $this.position().top;
|
||||
|
||||
// Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
|
||||
marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
|
||||
marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
|
||||
|
||||
offsetLeft = $this.offset().left - marginLeft;
|
||||
offsetTop = $this.offset().top - marginTop;
|
||||
|
||||
// Calculate the offset parent
|
||||
$this.parents().each(function() {
|
||||
var $this = $(this);
|
||||
|
||||
if ($this.data('stellar-offset-parent') === true) {
|
||||
parentOffsetLeft = tempParentOffsetLeft;
|
||||
parentOffsetTop = tempParentOffsetTop;
|
||||
$offsetParent = $this;
|
||||
|
||||
return false;
|
||||
} else {
|
||||
tempParentOffsetLeft += $this.position().left;
|
||||
tempParentOffsetTop += $this.position().top;
|
||||
}
|
||||
});
|
||||
|
||||
// Detect the offsets
|
||||
horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
|
||||
verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
|
||||
|
||||
// Add our object to the particles collection
|
||||
self.particles.push({
|
||||
$element: $this,
|
||||
$offsetParent: $offsetParent,
|
||||
isFixed: $this.css('position') === 'fixed',
|
||||
horizontalOffset: horizontalOffset,
|
||||
verticalOffset: verticalOffset,
|
||||
startingPositionLeft: positionLeft,
|
||||
startingPositionTop: positionTop,
|
||||
startingOffsetLeft: offsetLeft,
|
||||
startingOffsetTop: offsetTop,
|
||||
parentOffsetLeft: parentOffsetLeft,
|
||||
parentOffsetTop: parentOffsetTop,
|
||||
stellarRatio: ($this.data('stellar-ratio') !== undefined ? $this.data('stellar-ratio') : 1),
|
||||
width: $this.outerWidth(true),
|
||||
height: $this.outerHeight(true),
|
||||
isHidden: false
|
||||
});
|
||||
});
|
||||
},
|
||||
_findBackgrounds: function() {
|
||||
var self = this,
|
||||
scrollLeft = this._getScrollLeft(),
|
||||
scrollTop = this._getScrollTop(),
|
||||
$backgroundElements;
|
||||
|
||||
this.backgrounds = [];
|
||||
|
||||
if (!this.options.parallaxBackgrounds) return;
|
||||
|
||||
$backgroundElements = this.$element.find('[data-stellar-background-ratio]');
|
||||
|
||||
if (this.$element.data('stellar-background-ratio')) {
|
||||
$backgroundElements = $backgroundElements.add(this.$element);
|
||||
}
|
||||
|
||||
$backgroundElements.each(function() {
|
||||
var $this = $(this),
|
||||
backgroundPosition = getBackgroundPosition($this),
|
||||
horizontalOffset,
|
||||
verticalOffset,
|
||||
positionLeft,
|
||||
positionTop,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
$offsetParent,
|
||||
parentOffsetLeft = 0,
|
||||
parentOffsetTop = 0,
|
||||
tempParentOffsetLeft = 0,
|
||||
tempParentOffsetTop = 0;
|
||||
|
||||
// Ensure this element isn't already part of another scrolling element
|
||||
if (!$this.data('stellar-backgroundIsActive')) {
|
||||
$this.data('stellar-backgroundIsActive', this);
|
||||
} else if ($this.data('stellar-backgroundIsActive') !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save/restore the original top and left CSS values in case we destroy the instance
|
||||
if (!$this.data('stellar-backgroundStartingLeft')) {
|
||||
$this.data('stellar-backgroundStartingLeft', backgroundPosition[0]);
|
||||
$this.data('stellar-backgroundStartingTop', backgroundPosition[1]);
|
||||
} else {
|
||||
setBackgroundPosition($this, $this.data('stellar-backgroundStartingLeft'), $this.data('stellar-backgroundStartingTop'));
|
||||
}
|
||||
|
||||
// Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
|
||||
marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
|
||||
marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
|
||||
|
||||
offsetLeft = $this.offset().left - marginLeft - scrollLeft;
|
||||
offsetTop = $this.offset().top - marginTop - scrollTop;
|
||||
|
||||
// Calculate the offset parent
|
||||
$this.parents().each(function() {
|
||||
var $this = $(this);
|
||||
|
||||
if ($this.data('stellar-offset-parent') === true) {
|
||||
parentOffsetLeft = tempParentOffsetLeft;
|
||||
parentOffsetTop = tempParentOffsetTop;
|
||||
$offsetParent = $this;
|
||||
|
||||
return false;
|
||||
} else {
|
||||
tempParentOffsetLeft += $this.position().left;
|
||||
tempParentOffsetTop += $this.position().top;
|
||||
}
|
||||
});
|
||||
|
||||
// Detect the offsets
|
||||
horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
|
||||
verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
|
||||
|
||||
self.backgrounds.push({
|
||||
$element: $this,
|
||||
$offsetParent: $offsetParent,
|
||||
isFixed: $this.css('background-attachment') === 'fixed',
|
||||
horizontalOffset: horizontalOffset,
|
||||
verticalOffset: verticalOffset,
|
||||
startingValueLeft: backgroundPosition[0],
|
||||
startingValueTop: backgroundPosition[1],
|
||||
startingBackgroundPositionLeft: (isNaN(parseInt(backgroundPosition[0], 10)) ? 0 : parseInt(backgroundPosition[0], 10)),
|
||||
startingBackgroundPositionTop: (isNaN(parseInt(backgroundPosition[1], 10)) ? 0 : parseInt(backgroundPosition[1], 10)),
|
||||
startingPositionLeft: $this.position().left,
|
||||
startingPositionTop: $this.position().top,
|
||||
startingOffsetLeft: offsetLeft,
|
||||
startingOffsetTop: offsetTop,
|
||||
parentOffsetLeft: parentOffsetLeft,
|
||||
parentOffsetTop: parentOffsetTop,
|
||||
stellarRatio: ($this.data('stellar-background-ratio') === undefined ? 1 : $this.data('stellar-background-ratio'))
|
||||
});
|
||||
});
|
||||
},
|
||||
_reset: function() {
|
||||
var particle,
|
||||
startingPositionLeft,
|
||||
startingPositionTop,
|
||||
background,
|
||||
i;
|
||||
|
||||
for (i = this.particles.length - 1; i >= 0; i--) {
|
||||
particle = this.particles[i];
|
||||
startingPositionLeft = particle.$element.data('stellar-startingLeft');
|
||||
startingPositionTop = particle.$element.data('stellar-startingTop');
|
||||
|
||||
this._setPosition(particle.$element, startingPositionLeft, startingPositionLeft, startingPositionTop, startingPositionTop);
|
||||
|
||||
this.options.showElement(particle.$element);
|
||||
|
||||
particle.$element.data('stellar-startingLeft', null).data('stellar-elementIsActive', null).data('stellar-backgroundIsActive', null);
|
||||
}
|
||||
|
||||
for (i = this.backgrounds.length - 1; i >= 0; i--) {
|
||||
background = this.backgrounds[i];
|
||||
|
||||
background.$element.data('stellar-backgroundStartingLeft', null).data('stellar-backgroundStartingTop', null);
|
||||
|
||||
setBackgroundPosition(background.$element, background.startingValueLeft, background.startingValueTop);
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
this._reset();
|
||||
|
||||
this.$scrollElement.unbind('resize.' + this.name).unbind('scroll.' + this.name);
|
||||
this._animationLoop = $.noop;
|
||||
|
||||
$(window).unbind('load.' + this.name).unbind('resize.' + this.name);
|
||||
},
|
||||
_setOffsets: function() {
|
||||
var self = this,
|
||||
$window = $(window);
|
||||
|
||||
$window.unbind('resize.horizontal-' + this.name).unbind('resize.vertical-' + this.name);
|
||||
|
||||
if (typeof this.options.horizontalOffset === 'function') {
|
||||
this.horizontalOffset = this.options.horizontalOffset();
|
||||
$window.bind('resize.horizontal-' + this.name, function() {
|
||||
self.horizontalOffset = self.options.horizontalOffset();
|
||||
});
|
||||
} else {
|
||||
this.horizontalOffset = this.options.horizontalOffset;
|
||||
}
|
||||
|
||||
if (typeof this.options.verticalOffset === 'function') {
|
||||
this.verticalOffset = this.options.verticalOffset();
|
||||
$window.bind('resize.vertical-' + this.name, function() {
|
||||
self.verticalOffset = self.options.verticalOffset();
|
||||
});
|
||||
} else {
|
||||
this.verticalOffset = this.options.verticalOffset;
|
||||
}
|
||||
},
|
||||
_repositionElements: function() {
|
||||
var scrollLeft = this._getScrollLeft(),
|
||||
scrollTop = this._getScrollTop(),
|
||||
horizontalOffset,
|
||||
verticalOffset,
|
||||
particle,
|
||||
fixedRatioOffset,
|
||||
background,
|
||||
bgLeft,
|
||||
bgTop,
|
||||
isVisibleVertical = true,
|
||||
isVisibleHorizontal = true,
|
||||
newPositionLeft,
|
||||
newPositionTop,
|
||||
newOffsetLeft,
|
||||
newOffsetTop,
|
||||
i;
|
||||
|
||||
// First check that the scroll position or container size has changed
|
||||
if (this.currentScrollLeft === scrollLeft && this.currentScrollTop === scrollTop && this.currentWidth === this.viewportWidth && this.currentHeight === this.viewportHeight) {
|
||||
return;
|
||||
} else {
|
||||
this.currentScrollLeft = scrollLeft;
|
||||
this.currentScrollTop = scrollTop;
|
||||
this.currentWidth = this.viewportWidth;
|
||||
this.currentHeight = this.viewportHeight;
|
||||
}
|
||||
|
||||
// Reposition elements
|
||||
for (i = this.particles.length - 1; i >= 0; i--) {
|
||||
particle = this.particles[i];
|
||||
|
||||
fixedRatioOffset = (particle.isFixed ? 1 : 0);
|
||||
|
||||
// Calculate position, then calculate what the particle's new offset will be (for visibility check)
|
||||
if (this.options.horizontalScrolling) {
|
||||
newPositionLeft = (scrollLeft + particle.horizontalOffset + this.viewportOffsetLeft + particle.startingPositionLeft - particle.startingOffsetLeft + particle.parentOffsetLeft) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionLeft;
|
||||
newOffsetLeft = newPositionLeft - particle.startingPositionLeft + particle.startingOffsetLeft;
|
||||
} else {
|
||||
newPositionLeft = particle.startingPositionLeft;
|
||||
newOffsetLeft = particle.startingOffsetLeft;
|
||||
}
|
||||
|
||||
if (this.options.verticalScrolling) {
|
||||
newPositionTop = (scrollTop + particle.verticalOffset + this.viewportOffsetTop + particle.startingPositionTop - particle.startingOffsetTop + particle.parentOffsetTop) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionTop;
|
||||
newOffsetTop = newPositionTop - particle.startingPositionTop + particle.startingOffsetTop;
|
||||
} else {
|
||||
newPositionTop = particle.startingPositionTop;
|
||||
newOffsetTop = particle.startingOffsetTop;
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (this.options.hideDistantElements) {
|
||||
isVisibleHorizontal = !this.options.horizontalScrolling || newOffsetLeft + particle.width > (particle.isFixed ? 0 : scrollLeft) && newOffsetLeft < (particle.isFixed ? 0 : scrollLeft) + this.viewportWidth + this.viewportOffsetLeft;
|
||||
isVisibleVertical = !this.options.verticalScrolling || newOffsetTop + particle.height > (particle.isFixed ? 0 : scrollTop) && newOffsetTop < (particle.isFixed ? 0 : scrollTop) + this.viewportHeight + this.viewportOffsetTop;
|
||||
}
|
||||
|
||||
if (isVisibleHorizontal && isVisibleVertical) {
|
||||
if (particle.isHidden) {
|
||||
this.options.showElement(particle.$element);
|
||||
particle.isHidden = false;
|
||||
}
|
||||
|
||||
this._setPosition(particle.$element, newPositionLeft, particle.startingPositionLeft, newPositionTop, particle.startingPositionTop);
|
||||
} else {
|
||||
if (!particle.isHidden) {
|
||||
this.options.hideElement(particle.$element);
|
||||
particle.isHidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reposition backgrounds
|
||||
for (i = this.backgrounds.length - 1; i >= 0; i--) {
|
||||
background = this.backgrounds[i];
|
||||
|
||||
fixedRatioOffset = (background.isFixed ? 0 : 1);
|
||||
bgLeft = (this.options.horizontalScrolling ? (scrollLeft + background.horizontalOffset - this.viewportOffsetLeft - background.startingOffsetLeft + background.parentOffsetLeft - background.startingBackgroundPositionLeft) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueLeft);
|
||||
bgTop = (this.options.verticalScrolling ? (scrollTop + background.verticalOffset - this.viewportOffsetTop - background.startingOffsetTop + background.parentOffsetTop - background.startingBackgroundPositionTop) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueTop);
|
||||
|
||||
setBackgroundPosition(background.$element, bgLeft, bgTop);
|
||||
}
|
||||
},
|
||||
_handleScrollEvent: function() {
|
||||
var self = this,
|
||||
ticking = false;
|
||||
|
||||
var update = function() {
|
||||
self._repositionElements();
|
||||
ticking = false;
|
||||
};
|
||||
|
||||
var requestTick = function() {
|
||||
if (!ticking) {
|
||||
requestAnimFrame(update);
|
||||
ticking = true;
|
||||
}
|
||||
};
|
||||
|
||||
this.$scrollElement.bind('scroll.' + this.name, requestTick);
|
||||
requestTick();
|
||||
},
|
||||
_startAnimationLoop: function() {
|
||||
var self = this;
|
||||
|
||||
this._animationLoop = function() {
|
||||
requestAnimFrame(self._animationLoop);
|
||||
self._repositionElements();
|
||||
};
|
||||
this._animationLoop();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn[pluginName] = function (options) {
|
||||
var args = arguments;
|
||||
if (options === undefined || typeof options === 'object') {
|
||||
return this.each(function () {
|
||||
if (!$.data(this, 'plugin_' + pluginName)) {
|
||||
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
|
||||
}
|
||||
});
|
||||
} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
|
||||
return this.each(function () {
|
||||
var instance = $.data(this, 'plugin_' + pluginName);
|
||||
if (instance instanceof Plugin && typeof instance[options] === 'function') {
|
||||
instance[options].apply(instance, Array.prototype.slice.call(args, 1));
|
||||
}
|
||||
if (options === 'destroy') {
|
||||
$.data(this, 'plugin_' + pluginName, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$[pluginName] = function(options) {
|
||||
var $window = $(window);
|
||||
return $window.stellar.apply($window, Array.prototype.slice.call(arguments, 0));
|
||||
};
|
||||
|
||||
// Expose the scroll and position property function hashes so they can be extended
|
||||
$[pluginName].scrollProperty = scrollProperty;
|
||||
$[pluginName].positionProperty = positionProperty;
|
||||
|
||||
// Expose the plugin class so it can be modified
|
||||
window.Stellar = Plugin;
|
||||
}(jQuery, this, document));
|
File diff suppressed because one or more lines are too long
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"name": "jquery.stellar",
|
||||
"title": "Stellar.js",
|
||||
"version": "0.6.2",
|
||||
"description": "Parallax scrolling made easy.",
|
||||
"homepage": "http://markdalgleish.com/projects/stellar.js",
|
||||
"author": {
|
||||
"name": "Mark Dalgleish",
|
||||
"url": "http://markdalgleish.com"
|
||||
},
|
||||
"keywords": [
|
||||
"parallax",
|
||||
"scroll",
|
||||
"effect",
|
||||
"animation"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "http://markdalgleish.mit-license.org"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.4.3"
|
||||
},
|
||||
"bugs": "https://github.com/markdalgleish/stellar.js/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/markdalgleish/stellar.js.git"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Mark Dalgleish",
|
||||
"url": "http://markdalgleish.com"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"jquery.stellar.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.3.17"
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"name": "stellar",
|
||||
"title": "Stellar.js",
|
||||
"version": "0.6.2",
|
||||
"description": "Parallax scrolling made easy.",
|
||||
"homepage": "http://markdalgleish.com/projects/stellar.js",
|
||||
"author": {
|
||||
"name": "Mark Dalgleish",
|
||||
"url": "http://markdalgleish.com"
|
||||
},
|
||||
"keywords": [
|
||||
"parallax",
|
||||
"scroll",
|
||||
"effect",
|
||||
"animation"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "http://markdalgleish.mit-license.org"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.4.3"
|
||||
},
|
||||
"bugs": "https://github.com/markdalgleish/stellar.js/issues",
|
||||
"docs": "http://markdalgleish.com/projects/stellar.js/docs",
|
||||
"download": "https://github.com/markdalgleish/stellar.js#download"
|
||||
}
|
|
@ -113,7 +113,7 @@ table.editorbar-panel td.selected {
|
|||
|
||||
.oe_translate_or {
|
||||
color: white;
|
||||
padding: 0 0.2em;
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
.oe_translate_examples li {
|
||||
|
@ -141,6 +141,84 @@ table.editorbar-panel td.selected {
|
|||
background: #ffffb6;
|
||||
}
|
||||
|
||||
div.oe_menu_buttons {
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
ul.oe_menu_editor > li:first-child > div > i:before {
|
||||
content: "\f015";
|
||||
}
|
||||
ul.oe_menu_editor, ul.oe_menu_editor ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.oe_menu_editor ul {
|
||||
padding-left: 30px;
|
||||
}
|
||||
ul.oe_menu_editor button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
ul.oe_menu_editor li {
|
||||
margin: 5px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.oe_menu_editor .oe_menu_placeholder {
|
||||
outline: 1px dashed #4183c4;
|
||||
}
|
||||
ul.oe_menu_editor .mjs-nestedSortable-error {
|
||||
background: #fbe3e4;
|
||||
border-color: transparent;
|
||||
}
|
||||
ul.oe_menu_editor li div {
|
||||
border: 1px solid #d4d4d4;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
border-color: #d4d4d4 #d4d4d4 #bcbcbc;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
cursor: move;
|
||||
background: #f6f6f6;
|
||||
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(47%, #f6f6f6), color-stop(100%, #ededed));
|
||||
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
|
||||
background: -o-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
|
||||
background: -ms-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
|
||||
background: linear-gradient(to bottom, #ffffff 0%, #f6f6f6 47%, #ededed 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 );
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-branch div {
|
||||
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
|
||||
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-leaf div {
|
||||
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
|
||||
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div {
|
||||
border-color: #999999;
|
||||
background: #fafafa;
|
||||
}
|
||||
ul.oe_menu_editor .disclose {
|
||||
cursor: pointer;
|
||||
width: 10px;
|
||||
display: none;
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > ul {
|
||||
display: none;
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-branch > div > .disclose {
|
||||
display: inline-block;
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > div > .disclose > span:before {
|
||||
content: "+ ";
|
||||
}
|
||||
ul.oe_menu_editor li.mjs-nestedSortable-expanded > div > .disclose > span:before {
|
||||
content: "- ";
|
||||
}
|
||||
|
||||
/* ---- RTE ---- */
|
||||
.oe_editable .btn {
|
||||
-webkit-user-select: auto;
|
||||
|
@ -160,6 +238,14 @@ table.editorbar-panel td.selected {
|
|||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.modal.nosave .modal-footer button.save {
|
||||
display: none;
|
||||
}
|
||||
.modal.nosave .modal-footer button.wait {
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.cke_widget_wrapper {
|
||||
position: static !important;
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ table.editorbar-panel
|
|||
// ---- TRANSLATIONS ---- {{{
|
||||
.oe_translate_or
|
||||
color: white
|
||||
padding: 0 0.2em
|
||||
padding: 0 0 0 1em
|
||||
.oe_translate_examples li
|
||||
margin: 10px
|
||||
padding: 4px
|
||||
|
@ -124,6 +124,84 @@ table.editorbar-panel
|
|||
background: rgb(255, 255, 182)
|
||||
// }}}
|
||||
|
||||
// -------- MENU -------- {{{
|
||||
div.oe_menu_buttons
|
||||
top: -8px
|
||||
right: -8px
|
||||
ul.oe_menu_editor
|
||||
> li:first-child > div > i:before
|
||||
content: "\f015"
|
||||
&, & ul
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
& ul
|
||||
padding-left: 30px
|
||||
|
||||
button
|
||||
margin-left: 4px
|
||||
|
||||
li
|
||||
margin: 5px 0 0 0
|
||||
padding: 0
|
||||
|
||||
.oe_menu_placeholder
|
||||
outline: 1px dashed #4183C4
|
||||
|
||||
.mjs-nestedSortable-error
|
||||
background: #fbe3e4
|
||||
border-color: transparent
|
||||
|
||||
|
||||
// TODO: use compass mixins
|
||||
li div
|
||||
border: 1px solid #d4d4d4
|
||||
-webkit-border-radius: 3px
|
||||
-moz-border-radius: 3px
|
||||
border-radius: 3px
|
||||
border-color: #D4D4D4 #D4D4D4 #BCBCBC
|
||||
padding: 6px
|
||||
margin: 0
|
||||
cursor: move
|
||||
background: #f6f6f6
|
||||
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%)
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed))
|
||||
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
|
||||
background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
|
||||
background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
|
||||
background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 )
|
||||
|
||||
li.mjs-nestedSortable-branch div
|
||||
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #f0ece9 100%)
|
||||
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#f0ece9 100%)
|
||||
|
||||
li.mjs-nestedSortable-leaf div
|
||||
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #bcccbc 100%)
|
||||
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#bcccbc 100%)
|
||||
|
||||
li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div
|
||||
border-color: #999
|
||||
background: #fafafa
|
||||
|
||||
.disclose
|
||||
cursor: pointer
|
||||
width: 10px
|
||||
display: none
|
||||
|
||||
li.mjs-nestedSortable-collapsed > ul
|
||||
display: none
|
||||
|
||||
li.mjs-nestedSortable-branch > div > .disclose
|
||||
display: inline-block
|
||||
|
||||
li.mjs-nestedSortable-collapsed > div > .disclose > span:before
|
||||
content: '+ '
|
||||
|
||||
li.mjs-nestedSortable-expanded > div > .disclose > span:before
|
||||
content: '- '
|
||||
// }}}
|
||||
|
||||
/* ---- RTE ---- */
|
||||
|
||||
// bootstrap makes .btn elements unselectable -> RTE double-click can't know
|
||||
|
@ -141,6 +219,13 @@ table.editorbar-panel
|
|||
.modal .image-preview
|
||||
margin-bottom: 0.5em
|
||||
|
||||
.modal.nosave .modal-footer
|
||||
button.save
|
||||
display: none
|
||||
button.wait
|
||||
display: inline-block !important
|
||||
visibility: visible !important
|
||||
|
||||
// wrapper positioned relatively for drag&drop widget which is disabled below.
|
||||
// Breaks completely horribly crazy products listing page, so take it out.
|
||||
.cke_widget_wrapper
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY
|
||||
*
|
||||
* css for editor buttons, openerp widget included in the website and other
|
||||
* stuff must go to the editor.css
|
||||
* stuff must go to the editor.css
|
||||
*
|
||||
*/
|
||||
/* ----- GENERIC LAYOUTING HELPERS ---- */
|
||||
|
@ -239,7 +239,7 @@ footer {
|
|||
}
|
||||
|
||||
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before {
|
||||
content: "Click Edit To Create Content";
|
||||
content: "Press The Top-Left Edit Button";
|
||||
text-align: center;
|
||||
display: block;
|
||||
padding-top: 160px;
|
||||
|
@ -361,8 +361,7 @@ footer {
|
|||
}
|
||||
|
||||
.parallax {
|
||||
background-attachment: fixed;
|
||||
background-size: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.parallax.oe_small {
|
||||
height: 200px;
|
||||
|
@ -440,3 +439,28 @@ a[data-publish][data-publish='on']:hover .css_published {
|
|||
::selection {
|
||||
background: rgba(150, 150, 220, 0.3);
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.oe_demo {
|
||||
position: relative;
|
||||
}
|
||||
.oe_demo img {
|
||||
width: 100%;
|
||||
}
|
||||
.oe_demo div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0.85;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
padding: 7px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.oe_demo div a {
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
@charset "utf-8"
|
||||
|
||||
/*
|
||||
/*
|
||||
* THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY
|
||||
*
|
||||
* css for editor buttons, openerp widget included in the website and other
|
||||
* stuff must go to the editor.css
|
||||
* stuff must go to the editor.css
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -133,7 +133,7 @@ html,body, #wrapwrap
|
|||
|
||||
header, #wrap, footer
|
||||
display: table-row
|
||||
|
||||
|
||||
footer
|
||||
height: 100%
|
||||
background: rgb(239, 248, 248)
|
||||
|
@ -164,7 +164,7 @@ footer
|
|||
position: static
|
||||
|
||||
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before
|
||||
content: 'Click Edit To Create Content'
|
||||
content: 'Press The Top-Left Edit Button'
|
||||
text-align: center
|
||||
display: block
|
||||
padding-top: 160px
|
||||
|
@ -183,7 +183,7 @@ footer
|
|||
// .navbar .nav > li a
|
||||
// text-shadow: none
|
||||
|
||||
// .nav > li a
|
||||
// .nav > li a
|
||||
// display: block
|
||||
|
||||
.carousel-inner .item
|
||||
|
@ -276,15 +276,13 @@ footer
|
|||
background-color: grey
|
||||
|
||||
.parallax
|
||||
background-attachment: fixed
|
||||
background-size: 100%
|
||||
position: relative
|
||||
&.oe_small
|
||||
height: 200px
|
||||
&.oe_medium
|
||||
height: 300px
|
||||
&.oe_big
|
||||
height: 450px
|
||||
|
||||
/* -- Hack for removing double scrollbar from mobile preview -- */
|
||||
div#mobile-preview.modal
|
||||
overflow: hidden
|
||||
|
@ -335,3 +333,24 @@ a[data-publish]
|
|||
|
||||
::selection
|
||||
background: rgba(150, 150, 220, 0.3)
|
||||
|
||||
.logo-img
|
||||
width: 220px
|
||||
|
||||
|
||||
.oe_demo
|
||||
position: relative
|
||||
img
|
||||
width: 100%
|
||||
div
|
||||
position: absolute
|
||||
left: 0
|
||||
background-color: rgba(0,0,0,0.4)
|
||||
opacity: 0.85
|
||||
bottom: 0px
|
||||
width: 100%
|
||||
padding: 7px
|
||||
color: white
|
||||
font-weight: bold
|
||||
a
|
||||
color: white
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 190 KiB |
|
@ -6,7 +6,7 @@
|
|||
var hash = "#advanced-view-editor";
|
||||
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.ace.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.ace.xml');
|
||||
|
||||
website.ready().then(function () {
|
||||
if (window.location.hash.indexOf(hash) >= 0) {
|
||||
|
|
|
@ -4,17 +4,26 @@
|
|||
var website = openerp.website;
|
||||
// $.fn.data automatically parses value, '0'|'1' -> 0|1
|
||||
|
||||
website.templates.push('/website/static/src/xml/website.editor.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.editor.xml');
|
||||
website.dom_ready.done(function () {
|
||||
var is_smartphone = $(document.body)[0].clientWidth < 767;
|
||||
|
||||
if (!is_smartphone) {
|
||||
website.ready().then(website.init_editor);
|
||||
}
|
||||
|
||||
$(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
|
||||
// Prevent dropdown closing when a contenteditable children is focused
|
||||
if (ev.originalEvent
|
||||
&& $(ev.target).has(ev.originalEvent.target).length
|
||||
&& $(ev.originalEvent.target).is('[contenteditable]')) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function link_dialog(editor) {
|
||||
return new website.editor.LinkDialog(editor).appendTo(document.body);
|
||||
return new website.editor.RTELinkDialog(editor).appendTo(document.body);
|
||||
}
|
||||
function image_dialog(editor) {
|
||||
return new website.editor.RTEImageDialog(editor).appendTo(document.body);
|
||||
|
@ -54,6 +63,39 @@
|
|||
link_dialog(editor);
|
||||
}, null, null, 500);
|
||||
|
||||
var previousSelection;
|
||||
editor.on('selectionChange', function (evt) {
|
||||
var selected = evt.data.path.lastElement;
|
||||
if (previousSelection) {
|
||||
// cleanup previous selection
|
||||
$(previousSelection).next().remove();
|
||||
previousSelection = null;
|
||||
}
|
||||
if (!selected.is('img')
|
||||
|| selected.data('cke-realelement')
|
||||
|| selected.isReadOnly()
|
||||
|| selected.data('oe-model') === 'ir.ui.view') {
|
||||
return;
|
||||
}
|
||||
|
||||
// display button
|
||||
var $el = $(previousSelection = selected.$);
|
||||
var $btn = $('<button type="button" class="btn btn-primary" contenteditable="false">Edit</button>')
|
||||
.insertAfter($el)
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
image_dialog(editor);
|
||||
});
|
||||
|
||||
var position = $el.position();
|
||||
$btn.css({
|
||||
position: 'absolute',
|
||||
top: $el.height() / 2 + position.top - $btn.outerHeight() / 2,
|
||||
left: $el.width() / 2 + position.left - $btn.outerWidth() / 2,
|
||||
});
|
||||
});
|
||||
|
||||
//noinspection JSValidateTypes
|
||||
editor.addCommand('link', {
|
||||
exec: function (editor) {
|
||||
|
@ -360,8 +402,10 @@
|
|||
}
|
||||
});
|
||||
// Adding Static Menus
|
||||
menu.append('<li class="divider"></li><li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
|
||||
menu.append('<li class="divider"></li><li><a data-action="ace" href="#">Advanced view editor</a></li>');
|
||||
menu.append('<li class="divider"></li>');
|
||||
menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>');
|
||||
menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
|
||||
menu.append('<li><a href="/web#action=website.action_module_website">Install Apps</a></li>');
|
||||
self.trigger('rte:customize_menu_ready');
|
||||
}
|
||||
);
|
||||
|
@ -805,6 +849,106 @@
|
|||
this.pages = Object.create(null);
|
||||
this.text = null;
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
return $.when(
|
||||
this.fetch_pages().done(this.proxy('fill_pages')),
|
||||
this._super()
|
||||
).done(function () {
|
||||
self.bind_data();
|
||||
});
|
||||
},
|
||||
save: function () {
|
||||
var self = this, _super = this._super.bind(this);
|
||||
var $e = this.$('.list-group-item.active .url-source');
|
||||
var val = $e.val();
|
||||
if (!val || !$e[0].checkValidity()) {
|
||||
// FIXME: error message
|
||||
$e.closest('.form-group').addClass('has-error');
|
||||
$e.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var done = $.when();
|
||||
if ($e.hasClass('email-address')) {
|
||||
this.make_link('mailto:' + val, false, val);
|
||||
} else if ($e.hasClass('existing')) {
|
||||
self.make_link(val, false, this.pages[val]);
|
||||
} else if ($e.hasClass('pages')) {
|
||||
// Create the page, get the URL back
|
||||
done = $.get(_.str.sprintf(
|
||||
'/pagenew/%s?noredirect', encodeURI(val)))
|
||||
.then(function (response) {
|
||||
self.make_link(response, false, val);
|
||||
});
|
||||
} else {
|
||||
this.make_link(val, this.$('input.window-new').prop('checked'));
|
||||
}
|
||||
done.then(_super);
|
||||
},
|
||||
make_link: function (url, new_window, label) {
|
||||
},
|
||||
bind_data: function (text, href, new_window) {
|
||||
href = href || this.element && (this.element.data( 'cke-saved-href')
|
||||
|| this.element.getAttribute('href'));
|
||||
if (!href) { return; }
|
||||
|
||||
if (new_window === undefined) {
|
||||
new_window = this.element.getAttribute('target') === '_blank';
|
||||
}
|
||||
if (text === undefined) {
|
||||
text = this.element.getText();
|
||||
}
|
||||
|
||||
var match, $control;
|
||||
if ((match = /mailto:(.+)/.exec(href))) {
|
||||
$control = this.$('input.email-address').val(match[1]);
|
||||
} else if (href in this.pages) {
|
||||
$control = this.$('select.existing').val(href);
|
||||
} else if ((match = /\/page\/(.+)/.exec(href))) {
|
||||
var actual_href = '/page/website.' + match[1];
|
||||
if (actual_href in this.pages) {
|
||||
$control = this.$('select.existing').val(actual_href);
|
||||
}
|
||||
}
|
||||
if (!$control) {
|
||||
$control = this.$('input.url').val(href);
|
||||
}
|
||||
|
||||
this.changed($control);
|
||||
|
||||
this.$('input#link-text').val(text);
|
||||
this.$('input.window-new').prop('checked', new_window);
|
||||
},
|
||||
changed: function ($e) {
|
||||
this.$('.url-source').not($e).val('');
|
||||
$e.closest('.list-group-item')
|
||||
.addClass('active')
|
||||
.siblings().removeClass('active')
|
||||
.addBack().removeClass('has-error');
|
||||
},
|
||||
fetch_pages: function () {
|
||||
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'website',
|
||||
method: 'list_pages',
|
||||
args: [null],
|
||||
kwargs: {
|
||||
context: website.get_context()
|
||||
},
|
||||
});
|
||||
},
|
||||
fill_pages: function (results) {
|
||||
var self = this;
|
||||
var pages = this.$('select.existing')[0];
|
||||
_(results).each(function (result) {
|
||||
self.pages[result.url] = result.name;
|
||||
|
||||
pages.options[pages.options.length] =
|
||||
new Option(result.name, result.url);
|
||||
});
|
||||
},
|
||||
});
|
||||
website.editor.RTELinkDialog = website.editor.LinkDialog.extend({
|
||||
start: function () {
|
||||
var element;
|
||||
if ((element = this.get_selected_link()) && element.hasAttribute('href')) {
|
||||
|
@ -815,10 +959,7 @@
|
|||
this.add_removal_button();
|
||||
}
|
||||
|
||||
return $.when(
|
||||
this.fetch_pages().done(this.proxy('fill_pages')),
|
||||
this._super()
|
||||
).done(this.proxy('bind_data'));
|
||||
return this._super();
|
||||
},
|
||||
add_removal_button: function () {
|
||||
this.$('.modal-footer').prepend(
|
||||
|
@ -884,66 +1025,6 @@
|
|||
}, 0);
|
||||
}
|
||||
},
|
||||
save: function () {
|
||||
var self = this, _super = this._super.bind(this);
|
||||
var $e = this.$('.list-group-item.active .url-source');
|
||||
var val = $e.val();
|
||||
if (!val || !$e[0].checkValidity()) {
|
||||
// FIXME: error message
|
||||
$e.closest('.form-group').addClass('has-error');
|
||||
return;
|
||||
}
|
||||
|
||||
var done = $.when();
|
||||
if ($e.hasClass('email-address')) {
|
||||
this.make_link('mailto:' + val, false, val);
|
||||
} else if ($e.hasClass('existing')) {
|
||||
self.make_link(val, false, this.pages[val]);
|
||||
} else if ($e.hasClass('pages')) {
|
||||
// Create the page, get the URL back
|
||||
done = $.get(_.str.sprintf(
|
||||
'/pagenew/%s?noredirect', encodeURI(val)))
|
||||
.then(function (response) {
|
||||
self.make_link(response, false, val);
|
||||
});
|
||||
} else {
|
||||
this.make_link(val, this.$('input.window-new').prop('checked'));
|
||||
}
|
||||
done.then(_super);
|
||||
},
|
||||
bind_data: function () {
|
||||
var href = this.element && (this.element.data( 'cke-saved-href')
|
||||
|| this.element.getAttribute('href'));
|
||||
if (!href) { return; }
|
||||
|
||||
var match, $control;
|
||||
if (match = /mailto:(.+)/.exec(href)) {
|
||||
$control = this.$('input.email-address').val(match[1]);
|
||||
} else if (href in this.pages) {
|
||||
$control = this.$('select.existing').val(href);
|
||||
} else if (match = /\/page\/(.+)/.exec(href)) {
|
||||
var actual_href = '/page/website.' + match[1];
|
||||
if (actual_href in this.pages) {
|
||||
$control = this.$('select.existing').val(actual_href);
|
||||
}
|
||||
}
|
||||
if (!$control) {
|
||||
$control = this.$('input.url').val(href);
|
||||
}
|
||||
|
||||
this.changed($control);
|
||||
|
||||
this.$('input#link-text').val(this.element.getText());
|
||||
this.$('input.window-new').prop(
|
||||
'checked', this.element.getAttribute('target') === '_blank');
|
||||
},
|
||||
changed: function ($e) {
|
||||
this.$('.url-source').not($e).val('');
|
||||
$e.closest('.list-group-item')
|
||||
.addClass('active')
|
||||
.siblings().removeClass('active')
|
||||
.addBack().removeClass('has-error');
|
||||
},
|
||||
/**
|
||||
* CKEDITOR.plugins.link.getSelectedLink ignores the editor's root,
|
||||
* if the editor is set directly on a link it will thus not work.
|
||||
|
@ -951,27 +1032,8 @@
|
|||
get_selected_link: function () {
|
||||
return get_selected_link(this.editor);
|
||||
},
|
||||
fetch_pages: function () {
|
||||
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'website',
|
||||
method: 'list_pages',
|
||||
args: [null],
|
||||
kwargs: {
|
||||
context: website.get_context()
|
||||
},
|
||||
});
|
||||
},
|
||||
fill_pages: function (results) {
|
||||
var self = this;
|
||||
var pages = this.$('select.existing')[0];
|
||||
_(results).each(function (result) {
|
||||
self.pages[result.url] = result.name;
|
||||
|
||||
pages.options[pages.options.length] =
|
||||
new Option(result.name, result.url);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* ImageDialog widget. Lets users change an image, including uploading a
|
||||
* new image in OpenERP or selecting the image style (if supported by
|
||||
|
@ -1003,6 +1065,7 @@
|
|||
}),
|
||||
|
||||
start: function () {
|
||||
this.$('.modal-footer [disabled]').text("Uploading…");
|
||||
var $options = this.$('.image-style').children();
|
||||
this.image_styles = $options.map(function () { return this.value; }).get();
|
||||
|
||||
|
@ -1038,6 +1101,7 @@
|
|||
},
|
||||
|
||||
file_selection: function () {
|
||||
this.$el.addClass('nosave');
|
||||
this.$('button.filepicker').removeClass('btn-danger btn-success');
|
||||
|
||||
var self = this;
|
||||
|
@ -1060,6 +1124,7 @@
|
|||
this.set_image(url);
|
||||
},
|
||||
preview_image: function () {
|
||||
this.$el.removeClass('nosave');
|
||||
var image = this.$('input.url').val();
|
||||
if (!image) { return; }
|
||||
|
||||
|
|
|
@ -5,35 +5,30 @@
|
|||
// The following line can be removed in 2017
|
||||
openerp.website = website;
|
||||
|
||||
var templates = website.templates = [
|
||||
'/website/static/src/xml/website.xml'
|
||||
];
|
||||
|
||||
website.get_context = function (dict) {
|
||||
var html = document.documentElement;
|
||||
return _.extend({
|
||||
lang: html.getAttribute('lang').replace('-', '_')
|
||||
lang: html.getAttribute('lang').replace('-', '_'),
|
||||
website_id: html.getAttribute('data-website-id')|0
|
||||
}, dict);
|
||||
};
|
||||
|
||||
/* ----- TEMPLATE LOADING ---- */
|
||||
website.add_template = function(template) {
|
||||
templates.push(template);
|
||||
};
|
||||
website.load_templates = function(templates) {
|
||||
var dones = _(templates).map(function (t) {
|
||||
return new $.Deferred(function (d) {
|
||||
openerp.qweb.add_template(t, function(err) {
|
||||
if (err) {
|
||||
d.reject(err);
|
||||
} else {
|
||||
d.resolve();
|
||||
}
|
||||
});
|
||||
var templates_def = $.Deferred().resolve();
|
||||
website.add_template_file = function(template) {
|
||||
templates_def = templates_def.then(function() {
|
||||
var def = $.Deferred();
|
||||
openerp.qweb.add_template(template, function(err) {
|
||||
if (err) {
|
||||
def.reject(err);
|
||||
} else {
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
return def;
|
||||
});
|
||||
return $.when.apply(null, dones);
|
||||
};
|
||||
website.add_template_file('/website/static/src/xml/website.xml');
|
||||
website.reload = function () {
|
||||
location.hash = "scrollTop=" + window.document.body.scrollTop;
|
||||
if (location.search.indexOf("enable_editor") > -1) {
|
||||
|
@ -110,20 +105,33 @@
|
|||
*/
|
||||
website.ready = function() {
|
||||
if (!all_ready) {
|
||||
var tpl = website.load_templates(templates);
|
||||
all_ready = dom_ready.then(function () {
|
||||
all_ready = $.when(dom_ready, templates_def).then(function () {
|
||||
if ($('html').data('editable')) {
|
||||
website.id = $('html').data('website-id');
|
||||
website.session = new openerp.Session();
|
||||
var modules = ['website'];
|
||||
return openerp._t.database.load_translations(website.session, modules, website.get_context().lang);
|
||||
}
|
||||
}).then(tpl).promise();
|
||||
}).promise();
|
||||
}
|
||||
return all_ready;
|
||||
};
|
||||
|
||||
website.error = function(data, url) {
|
||||
var $error = $(openerp.qweb.render('website.error_dialog', {
|
||||
'title': data.data ? data.data.arguments[0] : data.statusText,
|
||||
'message': data.data ? data.data.arguments[1] : "",
|
||||
'backend_url': url
|
||||
}));
|
||||
$error.appendTo("body");
|
||||
$error.modal('show');
|
||||
};
|
||||
|
||||
dom_ready.then(function () {
|
||||
|
||||
/* ----- BOOTSTRAP STUFF ---- */
|
||||
$('.js_tooltip').bstooltip();
|
||||
|
||||
/* ----- PUBLISHING STUFF ---- */
|
||||
$('[data-publish]:has(.js_publish)').each(function () {
|
||||
var $pub = $("[data-publish]", this);
|
||||
|
@ -140,12 +148,15 @@
|
|||
|
||||
$(document).on('click', '.js_publish', function (e) {
|
||||
e.preventDefault();
|
||||
var $data = $(":first", this).parents("[data-publish]");
|
||||
$data.attr("data-publish", $data.first().attr("data-publish") == 'off' ? 'on' : 'off');
|
||||
openerp.jsonRpc('/website/publish', 'call', {'id': $(this).data('id'), 'object': $(this).data('object')})
|
||||
var $a = $(this);
|
||||
var $data = $a.find(":first").parents("[data-publish]");
|
||||
openerp.jsonRpc($a.data('controller') || '/website/publish', 'call', {'id': +$a.data('id'), 'object': $a.data('object')})
|
||||
.then(function (result) {
|
||||
$data.attr("data-publish", +result ? 'on' : 'off');
|
||||
}).fail(function (err, data) {
|
||||
website.error(data, '/web#model='+$a.data('object')+'&id='+$a.data('id'));
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', '.js_publish_management .js_publish_btn', function () {
|
||||
|
@ -156,11 +167,13 @@
|
|||
$data.toggleClass("css_unpublish css_publish");
|
||||
$btn.removeClass("btn-default btn-success");
|
||||
|
||||
openerp.jsonRpc('/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
|
||||
openerp.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
|
||||
.then(function (result) {
|
||||
$btn.toggleClass("btn-default", !result).toggleClass("btn-success", result);
|
||||
$data.toggleClass("css_unpublish", !result).toggleClass("css_publish", result);
|
||||
$data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off');
|
||||
}).fail(function (err, data) {
|
||||
website.error(data, '/web#model='+$data.data('object')+'&id='+$data.data('id'));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.menu = {};
|
||||
website.add_template_file('/website/static/src/xml/website.menu.xml');
|
||||
|
||||
website.EditorBar.include({
|
||||
events: _.extend({}, website.EditorBar.prototype.events, {
|
||||
'click a[data-action="edit-structure"]': 'editStructure',
|
||||
}),
|
||||
editStructure: function () {
|
||||
var context = website.get_context();
|
||||
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'website.menu',
|
||||
method: 'get_tree',
|
||||
args: [[context.website_id]],
|
||||
kwargs: {
|
||||
context: context
|
||||
},
|
||||
}).then(function (menu) {
|
||||
return new website.menu.EditMenuDialog(menu).appendTo(document.body);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
website.menu.EditMenuDialog = website.editor.Dialog.extend({
|
||||
template: 'website.menu.dialog.edit',
|
||||
events: _.extend({}, website.editor.Dialog.prototype.events, {
|
||||
'click button.js_add_menu': 'add_menu',
|
||||
'click button.js_edit_menu': 'edit_menu',
|
||||
'click button.js_delete_menu': 'delete_menu',
|
||||
}),
|
||||
init: function (menu) {
|
||||
this.menu = menu;
|
||||
this.root_menu_id = menu.id;
|
||||
this.flat = this.flatenize(menu);
|
||||
this.to_delete = [];
|
||||
this._super();
|
||||
},
|
||||
start: function () {
|
||||
var r = this._super.apply(this, arguments);
|
||||
var button = openerp.qweb.render('website.menu.dialog.footer-button');
|
||||
this.$('.modal-footer').prepend(button);
|
||||
this.$('.oe_menu_editor').nestedSortable({
|
||||
listType: 'ul',
|
||||
handle: 'div',
|
||||
items: 'li',
|
||||
maxLevels: 2,
|
||||
toleranceElement: '> div',
|
||||
forcePlaceholderSize: true,
|
||||
opacity: 0.6,
|
||||
placeholder: 'oe_menu_placeholder',
|
||||
tolerance: 'pointer',
|
||||
attribute: 'data-menu-id',
|
||||
expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*)
|
||||
});
|
||||
return r;
|
||||
},
|
||||
flatenize: function (node, dict) {
|
||||
dict = dict || {};
|
||||
var self = this;
|
||||
dict[node.id] = node;
|
||||
node.children.forEach(function (child) {
|
||||
self.flatenize(child, dict);
|
||||
});
|
||||
return dict;
|
||||
},
|
||||
add_menu: function () {
|
||||
var self = this;
|
||||
var dialog = new website.menu.MenuEntryDialog();
|
||||
dialog.on('add-menu', this, function (link) {
|
||||
var new_menu = {
|
||||
id: _.uniqueId('new-'),
|
||||
name: link[2] || link[0],
|
||||
url: link[0],
|
||||
new_window: link[1],
|
||||
parent_id: false,
|
||||
sequence: 0,
|
||||
children: [],
|
||||
};
|
||||
self.flat[new_menu.id] = new_menu;
|
||||
self.$('.oe_menu_editor').append(
|
||||
openerp.qweb.render(
|
||||
'website.menu.dialog.submenu', { submenu: new_menu }));
|
||||
});
|
||||
dialog.appendTo(document.body);
|
||||
},
|
||||
edit_menu: function (ev) {
|
||||
var self = this;
|
||||
var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
|
||||
var menu = self.flat[menu_id];
|
||||
if (menu) {
|
||||
var dialog = new website.menu.MenuEntryDialog(undefined, menu);
|
||||
dialog.on('update-menu', this, function (link) {
|
||||
var id = link.shift();
|
||||
var menu_obj = self.flat[id];
|
||||
_.extend(menu_obj, {
|
||||
name: link[2],
|
||||
url: link[0],
|
||||
new_window: link[1],
|
||||
});
|
||||
var $menu = self.$('[data-menu-id="' + id + '"]');
|
||||
$menu.find('> div > span').text(menu_obj.name);
|
||||
});
|
||||
dialog.appendTo(document.body);
|
||||
} else {
|
||||
alert("Could not find menu entry");
|
||||
}
|
||||
},
|
||||
delete_menu: function (ev) {
|
||||
var self = this;
|
||||
var $menu = $(ev.currentTarget).closest('[data-menu-id]');
|
||||
var mid = $menu.data('menu-id')|0;
|
||||
if (mid) {
|
||||
this.to_delete.push(mid);
|
||||
}
|
||||
$menu.remove();
|
||||
},
|
||||
save: function () {
|
||||
var self = this;
|
||||
var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0});
|
||||
var levels = [];
|
||||
var data = [];
|
||||
var context = website.get_context();
|
||||
// Resquence, re-tree and remove useless data
|
||||
new_menu.forEach(function (menu) {
|
||||
if (menu.item_id) {
|
||||
levels[menu.depth] = (levels[menu.depth] || 0) + 1;
|
||||
var mobj = self.flat[menu.item_id];
|
||||
mobj.sequence = levels[menu.depth];
|
||||
mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id;
|
||||
delete(mobj.children);
|
||||
data.push(mobj);
|
||||
}
|
||||
});
|
||||
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
||||
model: 'website.menu',
|
||||
method: 'save',
|
||||
args: [[context.website_id], { data: data, to_delete: self.to_delete }],
|
||||
kwargs: {
|
||||
context: context
|
||||
},
|
||||
}).then(function (menu) {
|
||||
self.close();
|
||||
website.reload();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
website.menu.MenuEntryDialog = website.editor.LinkDialog.extend({
|
||||
template: 'website.menu.dialog.add',
|
||||
init: function (editor, data) {
|
||||
this.data = data;
|
||||
this.update_mode = !!this.data;
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
return $.when(this._super.apply(this, arguments)).then(function () {
|
||||
if (self.data) {
|
||||
self.bind_data(self.data.name, self.data.url, self.data.new_window);
|
||||
}
|
||||
var $link_text = self.$('#link-text').focus();
|
||||
self.$('#link-existing').change(function () {
|
||||
if (!$link_text.val()) {
|
||||
$link_text.val($(this).find('option:selected').text());
|
||||
$link_text.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
save: function () {
|
||||
var $e = this.$('#link-text');
|
||||
if (!$e.val() || !$e[0].checkValidity()) {
|
||||
$e.closest('.form-group').addClass('has-error');
|
||||
$e.focus();
|
||||
return;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
make_link: function (url, new_window, label) {
|
||||
var menu_label = this.$('input#link-text').val() || label;
|
||||
if (this.data) {
|
||||
this.trigger('update-menu', [this.data.id, url, new_window, menu_label]);
|
||||
} else {
|
||||
this.trigger('add-menu', [url, new_window, menu_label]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
})();
|
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.seo.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.seo.xml');
|
||||
|
||||
website.EditorBar.include({
|
||||
events: _.extend({}, website.EditorBar.prototype.events, {
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
var start_snippet_animation = function () {
|
||||
hack_to_add_snippet_id();
|
||||
$("[data-snippet-id]").each(function() {
|
||||
var $snipped_id = $(this);
|
||||
if ( !$snipped_id.parents("#oe_snippets").length &&
|
||||
typeof $snipped_id.data("snippet-view") === 'undefined' &&
|
||||
website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) {
|
||||
var snippet = new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id);
|
||||
$snipped_id.data("snippet-view", snippet);
|
||||
}
|
||||
});
|
||||
};
|
||||
$(document).ready(start_snippet_animation);
|
||||
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.snippets.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.snippets.xml');
|
||||
|
||||
website.EditorBar.include({
|
||||
start: function () {
|
||||
|
@ -17,9 +32,17 @@
|
|||
return this._super();
|
||||
},
|
||||
edit: function () {
|
||||
var self = this;
|
||||
$("body").off('click');
|
||||
window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
|
||||
this.snippets.appendTo(this.$el);
|
||||
|
||||
this.on('rte:ready', this, function () {
|
||||
self.snippets.$button.removeClass("hidden");
|
||||
start_snippet_animation();
|
||||
self.trigger('rte:snippets_ready');
|
||||
});
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
save: function () {
|
||||
|
@ -33,20 +56,8 @@
|
|||
},
|
||||
});
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
hack_to_add_snippet_id();
|
||||
$("[data-snippet-id]").each(function() {
|
||||
var $snipped_id = $(this);
|
||||
if (typeof $snipped_id.data("snippet-view") === 'undefined' &&
|
||||
website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) {
|
||||
$snipped_id.data("snippet-view", new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ----- SNIPPET SELECTOR ---- */
|
||||
|
||||
|
||||
website.snippet = {};
|
||||
var observer = new website.Observer(function (mutations) {
|
||||
if (!_(mutations).find(function (m) {
|
||||
|
@ -118,10 +129,11 @@
|
|||
start: function() {
|
||||
var self = this;
|
||||
|
||||
var $ul = this.parent.$("#website-top-edit ul");
|
||||
this.$button = $(openerp.qweb.render('website.snippets_button'))
|
||||
.prependTo(this.parent.$("#website-top-edit ul"))
|
||||
.find("button");
|
||||
|
||||
var $button = $(openerp.qweb.render('website.snippets_button')).prependTo($ul);
|
||||
$button.find('button').click(function () {
|
||||
this.$button.click(function () {
|
||||
self.make_active(false);
|
||||
self.$el.toggleClass("hidden");
|
||||
});
|
||||
|
@ -237,6 +249,7 @@
|
|||
}
|
||||
if (this.$active_snipped_id) {
|
||||
this.snippet_blur(this.$active_snipped_id);
|
||||
this.$active_snipped_id = false;
|
||||
}
|
||||
if ($snipped_id) {
|
||||
if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) {
|
||||
|
@ -245,8 +258,6 @@
|
|||
this.$active_snipped_id = $snipped_id;
|
||||
this.create_overlay(this.$active_snipped_id);
|
||||
this.snippet_focus($snipped_id);
|
||||
} else {
|
||||
self.$active_snipped_id = false;
|
||||
}
|
||||
},
|
||||
create_overlay: function ($snipped_id) {
|
||||
|
@ -361,7 +372,7 @@
|
|||
}
|
||||
|
||||
self.create_overlay($target);
|
||||
$target.data("snippet-editor").build_snippet($target);
|
||||
$target.data("snippet-editor").drop_and_build_snippet($target);
|
||||
|
||||
} else {
|
||||
$target = $(this).data('target');
|
||||
|
@ -369,7 +380,7 @@
|
|||
self.create_overlay($target);
|
||||
if (website.snippet.editorRegistry[snipped_id]) {
|
||||
var snippet = new website.snippet.editorRegistry[snipped_id](self, $target);
|
||||
snippet.build_snippet($target);
|
||||
snippet.drop_and_build_snippet($target);
|
||||
}
|
||||
}
|
||||
setTimeout(function () {self.make_active($target);},0);
|
||||
|
@ -498,7 +509,7 @@
|
|||
console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute.");
|
||||
console.debug( "Wrong node(s): ", selector);
|
||||
}
|
||||
|
||||
|
||||
function is_visible($el){
|
||||
return $el.css('display') != 'none'
|
||||
&& $el.css('opacity') != '0'
|
||||
|
@ -513,7 +524,7 @@
|
|||
var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
|
||||
return parents.length === 0;
|
||||
});
|
||||
|
||||
|
||||
$targets.each(function () {
|
||||
var $target = $(this);
|
||||
if (!$target.data('overlay')) {
|
||||
|
@ -552,13 +563,13 @@
|
|||
start: function () {
|
||||
},
|
||||
/* onFocusEdit
|
||||
* if they are an editor for this data-snippet-id
|
||||
* if they are an editor for this data-snippet-id
|
||||
* Called before onFocus of snippet editor
|
||||
*/
|
||||
onFocusEdit : function () {},
|
||||
|
||||
/* onBlurEdit
|
||||
* if they are an editor for this data-snippet-id
|
||||
* if they are an editor for this data-snippet-id
|
||||
* Called after onBlur of snippet editor
|
||||
*/
|
||||
onBlurEdit : function () {},
|
||||
|
@ -589,7 +600,8 @@
|
|||
* Displayed into the overlay options on focus
|
||||
*/
|
||||
_readXMLData: function() {
|
||||
this.$el = this.parent.$snippets.siblings("[data-snippet-id='"+this.snippet_id+"']").clone();
|
||||
var self = this;
|
||||
this.$el = this.parent.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
|
||||
this.$editor = this.$el.find(".oe_snippet_options");
|
||||
var $options = this.$overlay.find(".oe_overlay_options");
|
||||
this.$editor.prependTo($options.find(".oe_options ul"));
|
||||
|
@ -658,7 +670,7 @@
|
|||
});
|
||||
|
||||
$("body").addClass('move-important');
|
||||
|
||||
|
||||
self._drag_and_drop_after_insert_dropzone();
|
||||
self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
|
||||
},
|
||||
|
@ -710,7 +722,7 @@
|
|||
|
||||
if (website.snippet.editorRegistry[snipped_id]) {
|
||||
var snippet = new website.snippet.editorRegistry[snipped_id](this, this.$target);
|
||||
snippet.build_snippet(this.$target);
|
||||
snippet.drop_and_build_snippet(this.$target);
|
||||
}
|
||||
var _class = "oe_snippet_" + snipped_id + " " + ($li.data("class") || "");
|
||||
if (active) {
|
||||
|
@ -749,6 +761,7 @@
|
|||
var index = _.indexOf(self.parent.snippets, self.$target.get(0));
|
||||
delete self.parent.snippets[index];
|
||||
self.$target.remove();
|
||||
self.$overlay.remove();
|
||||
return false;
|
||||
});
|
||||
this._drag_and_drop();
|
||||
|
@ -756,11 +769,11 @@
|
|||
},
|
||||
|
||||
/*
|
||||
* build_snippet
|
||||
* drop_and_build_snippet
|
||||
* This method is called just after that a thumbnail is drag and dropped into a drop zone
|
||||
* (after the insertion of this.$body, if this.$body exists)
|
||||
*/
|
||||
build_snippet: function ($target) {
|
||||
drop_and_build_snippet: function ($target) {
|
||||
},
|
||||
|
||||
/* onFocus
|
||||
|
@ -847,7 +860,7 @@
|
|||
if (!resize_values.s) $box.find(".oe_handle.s").remove();
|
||||
if (!resize_values.e) $box.find(".oe_handle.e").remove();
|
||||
if (!resize_values.w) $box.find(".oe_handle.w").remove();
|
||||
|
||||
|
||||
this.$overlay.append($box.find(".oe_handles").html());
|
||||
|
||||
this.$overlay.find(".oe_handle").on('mousedown', function (event){
|
||||
|
@ -1026,7 +1039,7 @@
|
|||
},
|
||||
});
|
||||
website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({
|
||||
build_snippet: function() {
|
||||
drop_and_build_snippet: function() {
|
||||
var id = 0;
|
||||
$("body .carousel").each(function () {
|
||||
var _id = +$(this).attr("id").replace(/^myCarousel/, '');
|
||||
|
@ -1037,6 +1050,7 @@
|
|||
this.$target.attr("id", "myCarousel" + id);
|
||||
this.$target.find(".carousel-control").attr("href", "#myCarousel" + id);
|
||||
this.$target.find("[data-target='#myCarousel']").attr("data-target", "#myCarousel" + id);
|
||||
|
||||
this.rebind_event();
|
||||
},
|
||||
onFocus: function () {
|
||||
|
@ -1053,6 +1067,9 @@
|
|||
if(!this.$target.find(".item.active").length) {
|
||||
this.$target.find(".item:first").addClass("active");
|
||||
}
|
||||
this.$target.removeAttr('contentEditable')
|
||||
.find('.content, .carousel-image img')
|
||||
.removeAttr('contentEditable');
|
||||
},
|
||||
start : function () {
|
||||
this._super();
|
||||
|
@ -1087,6 +1104,10 @@
|
|||
self.$target.carousel($(this).data('slide')); });
|
||||
this.$target.off('click').on('click', '.carousel-indicators [data-target]', function () {
|
||||
self.$target.carousel(+$(this).data('slide-to')); });
|
||||
|
||||
this.$target.attr('contentEditable', 'false')
|
||||
.find('.content, .carousel-image img')
|
||||
.attr('contentEditable', 'true');
|
||||
},
|
||||
on_add: function (e) {
|
||||
e.preventDefault();
|
||||
|
@ -1095,7 +1116,7 @@
|
|||
var index = $active.index();
|
||||
this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden");
|
||||
this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
|
||||
|
||||
|
||||
var $clone = this.$el.find(".item.active").clone();
|
||||
var bg = this.$editor.find('ul[name="carousel-background"] li:not([data-value="'+ $active.css("background-image").replace(/.*:\/\/[^\/]+|\)$/g, '') +'"]):first').data("value");
|
||||
$clone.css("background-image", "url('"+ bg +"')");
|
||||
|
@ -1191,24 +1212,23 @@
|
|||
website.snippet.editorRegistry.parallax = website.snippet.editorRegistry.resize.extend({
|
||||
start : function () {
|
||||
this._super();
|
||||
this.change_background($('.parallax', this.$target), 'ul[name="parallax-background"]');
|
||||
this.change_background(this.$target, 'ul[name="parallax-background"]');
|
||||
this.scroll();
|
||||
this.change_size();
|
||||
},
|
||||
scroll: function(){
|
||||
scroll: function () {
|
||||
var self = this;
|
||||
var $ul = this.$editor.find('ul[name="parallax-scroll"]');
|
||||
var $li = $ul.find("li");
|
||||
var $parallax = this.$target.find('.parallax');
|
||||
var speed = $parallax.data('stellar-background-ratio') || 0.5 ;
|
||||
|
||||
var speed = this.$target.data('scroll-background-ratio') || 0.6 ;
|
||||
$ul.find('[data-value="' + speed + '"]').addClass('active');
|
||||
$li.on('click', function (event) {
|
||||
$li.removeClass("active");
|
||||
$(this).addClass("active");
|
||||
var speed = $(this).data('value');
|
||||
$parallax.attr('data-stellar-background-ratio', speed);
|
||||
self.$target.attr('data-scroll-background-ratio', speed);
|
||||
self.$target.data("snippet-view").set_values();
|
||||
});
|
||||
|
||||
},
|
||||
clean_for_save: function () {
|
||||
this._super();
|
||||
|
@ -1216,14 +1236,12 @@
|
|||
},
|
||||
change_size: function () {
|
||||
var self = this;
|
||||
var $el = $('.oe_big,.oe_medium,.oe_small', this.$target);
|
||||
|
||||
var size = 'oe_big';
|
||||
if ($el.hasClass('oe_small'))
|
||||
if (this.$target.hasClass('oe_small'))
|
||||
size = 'oe_small';
|
||||
else if ($el.hasClass('oe_medium'))
|
||||
else if (this.$target.hasClass('oe_medium'))
|
||||
size = 'oe_medium';
|
||||
|
||||
var $ul = this.$editor.find('ul[name="parallax-size"]');
|
||||
var $li = $ul.find("li");
|
||||
|
||||
|
@ -1232,21 +1250,50 @@
|
|||
$li.on('click', function (event) {
|
||||
$li.removeClass("active");
|
||||
$(this).addClass("active");
|
||||
self.$target.data("snippet-view").set_values();
|
||||
})
|
||||
.on('mouseover', function (event) {
|
||||
$el.removeClass('oe_big oe_small oe_medium');
|
||||
$el.addClass($(event.currentTarget).data("value"));
|
||||
self.$target.removeClass('oe_big oe_small oe_medium');
|
||||
self.$target.addClass($(event.currentTarget).data("value"));
|
||||
})
|
||||
.on('mouseout', function (event) {
|
||||
$el.removeClass('oe_big oe_small oe_medium');
|
||||
$el.addClass($ul.find('li.active').data("value"));
|
||||
self.$target.removeClass('oe_big oe_small oe_medium');
|
||||
self.$target.addClass($ul.find('li.active').data("value"));
|
||||
});
|
||||
}
|
||||
});
|
||||
website.snippet.animationRegistry.parallax = website.snippet.Animation.extend({
|
||||
start: function () {
|
||||
$.stellar({ horizontalScrolling: false, verticalOffset: 0 });
|
||||
var self = this;
|
||||
this.set_values();
|
||||
var on_scroll = function () {
|
||||
var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0);
|
||||
if (speed == 1) return;
|
||||
var offset = parseFloat(self.$target.attr("data-scroll-background-offset") || 0);
|
||||
var top = offset + window.scrollY * speed;
|
||||
self.$target.css("background-position", "0px " + top + "px");
|
||||
};
|
||||
$(window).off("scroll").on("scroll", on_scroll);
|
||||
},
|
||||
set_values: function () {
|
||||
var self = this;
|
||||
var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0);
|
||||
|
||||
if (speed == 1) {
|
||||
this.$target.css("background-attachment", "fixed").css("background-position", "0px 0px");
|
||||
return;
|
||||
} else {
|
||||
this.$target.css("background-attachment", "scroll");
|
||||
}
|
||||
|
||||
this.$target.attr("data-scroll-background-offset", 0);
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
self.$target.attr("data-scroll-background-offset", self.$target.outerHeight() - this.height);
|
||||
$(window).scroll();
|
||||
};
|
||||
img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, "");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
|
|
|
@ -24,10 +24,14 @@
|
|||
stepId: 'edit-page',
|
||||
element: 'button[data-action=edit]',
|
||||
placement: 'bottom',
|
||||
reflex: true,
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
editor.on('rte:snippets_ready', editor, function() {
|
||||
self.movetoStep('add-block');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'add-block',
|
||||
|
@ -37,11 +41,6 @@
|
|||
content: "To add content in a page, you can insert building blocks.",
|
||||
template: render('website.tour_popover'),
|
||||
onShow: function () {
|
||||
function refreshAddBlockStep () {
|
||||
self.tour.showStep(self.indexOfStep('add-block'));
|
||||
editor.off('rte:ready', editor, refreshAddBlockStep);
|
||||
}
|
||||
editor.on('rte:ready', editor, refreshAddBlockStep);
|
||||
$('button[data-action=snippet]').click(function () {
|
||||
self.movetoStep('drag-banner');
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.tour.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.tour.xml');
|
||||
|
||||
website.tour = {
|
||||
render: function render (template, dict) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
var website = openerp.website;
|
||||
website.templates.push('/website/static/src/xml/website.translator.xml');
|
||||
website.add_template_file('/website/static/src/xml/website.translator.xml');
|
||||
var nodialog = 'website_translator_nodialog';
|
||||
|
||||
website.EditorBar.include({
|
||||
|
@ -16,8 +16,7 @@
|
|||
self.$('button[data-action=edit]')
|
||||
.text("Translate")
|
||||
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
|
||||
self.$('[data-action=snippet]').hide();
|
||||
self.$('#customize-menu-button').hide();
|
||||
self.$('.js_hide_on_translate').hide();
|
||||
});
|
||||
},
|
||||
edit: function () {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<div class="modal-body"><t t-raw="__content__"/></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" data-dismiss="modal" aria-hidden="true">Discard</button>
|
||||
<button type="button" class="btn hidden wait" disabled="disabled"/>
|
||||
<button type="button" class="btn btn-primary save">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,22 +40,22 @@
|
|||
<form>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item form-group active">
|
||||
<h3 class="list-group-item-heading">
|
||||
<h4 class="list-group-item-heading">
|
||||
<label for="link-existing" class="control-label">
|
||||
Existing page
|
||||
</label>
|
||||
</h3>
|
||||
</h4>
|
||||
<select class="existing form-control url-source"
|
||||
id="link-existing">
|
||||
<option/>
|
||||
</select>
|
||||
</li>
|
||||
<li class="list-group-item form-group">
|
||||
<h3 class="list-group-item-heading">
|
||||
<h4 class="list-group-item-heading">
|
||||
<label for="link-new" class="control-label">
|
||||
New page
|
||||
</label>
|
||||
</h3>
|
||||
</h4>
|
||||
<input type="text" class="form-control pages url-source"
|
||||
id="link-new" placeholder="Page Name"/>
|
||||
</li>
|
||||
|
@ -65,20 +66,20 @@
|
|||
Open in new window
|
||||
</label>
|
||||
</div>
|
||||
<h3 class="list-group-item-heading">
|
||||
<h4 class="list-group-item-heading">
|
||||
<label for="link-external" class="control-label">
|
||||
External Website
|
||||
URL
|
||||
</label>
|
||||
</h3>
|
||||
</h4>
|
||||
<input type="text" class="form-control url url-source"
|
||||
id="link-external" placeholder="http://openerp.com"/>
|
||||
</li>
|
||||
<li class="list-group-item form-group">
|
||||
<h3 class="list-group-item-heading">
|
||||
<h4 class="list-group-item-heading">
|
||||
<label for="link-email" class="control-label">
|
||||
Email Address
|
||||
</label>
|
||||
</h3>
|
||||
</h4>
|
||||
<input type="email" class="form-control email-address url-source"
|
||||
id="link-email" placeholder="you@yourwebsite.com"/>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="website.menu.dialog.submenu">
|
||||
<li t-att-data-menu-id="submenu.id">
|
||||
<div>
|
||||
<i class="icon-reorder"/>
|
||||
<span><t t-esc="submenu.name"/></span>
|
||||
<div class="btn-group btn-group-xs pull-right oe_menu_buttons">
|
||||
<button type="button" class="btn js_edit_menu">
|
||||
<i class="icon-edit"/>
|
||||
</button>
|
||||
<button type="button" class="btn js_delete_menu">
|
||||
<i class="icon-trash"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<t t-set="children" t-value="submenu.children"/>
|
||||
<ul t-if="children">
|
||||
<t t-foreach="children" t-as="submenu">
|
||||
<t t-call="website.menu.dialog.submenu"/>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
<t t-name="website.menu.dialog.footer-button">
|
||||
<button type="button" class="btn pull-left js_add_menu btn-success">
|
||||
<i class="icon-plus-sign"/> Add Menu Entry
|
||||
</button>
|
||||
</t>
|
||||
<t t-name="website.menu.dialog.edit">
|
||||
<t t-call="website.editor.dialog">
|
||||
<t t-set="title">Edit Structure</t>
|
||||
<ul class="oe_menu_editor">
|
||||
<t t-foreach="widget.menu.children" t-as="submenu">
|
||||
<t t-call="website.menu.dialog.submenu"/>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="website.menu.dialog.add" t-extend="website.editor.dialog.link">
|
||||
<t t-jquery="t[t-set='title']" t-operation="inner">
|
||||
<t t-if="!widget.update_mode">Add Menu Entry</t>
|
||||
<t t-if="widget.update_mode">Edit Menu Entry</t>
|
||||
</t>
|
||||
<t t-jquery="form > div.form-horizontal" t-operation="replace"/>
|
||||
<t t-jquery="ul.list-group" t-operation="before">
|
||||
<div class="list-group">
|
||||
<div class="form-group list-group-item active">
|
||||
<h3 class="list-group-item-heading">
|
||||
<label for="link-new" class="control-label">
|
||||
Menu Label
|
||||
</label>
|
||||
</h3>
|
||||
<input type="text" class="form-control"
|
||||
id="link-text" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
|
@ -32,7 +32,7 @@
|
|||
<!-- filled in JS -->
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="page-header">2. Reference Your Page <small>using above suggestions</small></h3>
|
||||
<h3 class="page-header">2. Reference Your Page <small>using above suggested keywords</small></h3>
|
||||
<div class="form-horizontal mt16" role="form">
|
||||
<div class="form-group">
|
||||
<label for="seo_page_title" class="col-lg-2 control-label">Title</label>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Snippet loader -->
|
||||
<t t-name="website.snippets_button">
|
||||
<li class="navbar-form"><button type="button" data-action="snippet" class="btn btn-primary">Insert Blocks</button></li>
|
||||
<li class="navbar-form js_hide_on_translate"><button type="button" data-action="snippet" class="hidden btn btn-primary">Insert Blocks</button></li>
|
||||
</t>
|
||||
<t t-name="website.snippets_style">
|
||||
<li class="navbar-form">
|
||||
|
@ -63,7 +63,7 @@
|
|||
<div class="btn-group">
|
||||
<a href="#" class="btn btn-default btn-sm oe_snippet_parent" title="Select Container Block"><span class="icon-upload-alt"/></a>
|
||||
<div class="dropdown oe_options hidden btn-group">
|
||||
<a href="#" data-toggle="dropdown" class="btn btn-default btn-sm" title="Customize">Customize <span class="caret"/></a>
|
||||
<a href="#" data-toggle="dropdown" class="btn btn-primary btn-sm" title="Customize">Customize <span class="caret"/></a>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</div>
|
||||
<a href="#" class="btn btn-default btn-sm oe_snippet_move" title="Drag to Move">&nbsp; <span class="icon-move"/> &nbsp;</a>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="website.TranslatorAdditionalButtons">
|
||||
<strong class="oe_translate_or">or</strong>
|
||||
<a class="btn btn-default" data-action="edit_master" href="#">Edit Master</a>
|
||||
<span class="oe_translate_or">or</span>
|
||||
<a class="btn btn-link" data-action="edit_master" href="#">Edit Master</a>
|
||||
</t>
|
||||
<t t-name="website.TranslatorDialog">
|
||||
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">
|
||||
|
@ -18,22 +18,23 @@
|
|||
You are about to enter the translation mode.
|
||||
</p>
|
||||
<p>
|
||||
In this mode you can not change the structure of the document, you can only translate text.
|
||||
</p>
|
||||
<p>
|
||||
Here are the visuals used in order to help you in your translation flow:
|
||||
Here are the visuals used to help you translate efficiently:
|
||||
<ul class="oe_translate_examples">
|
||||
<li class="oe_translatable_text">
|
||||
Normal translatable content
|
||||
</li>
|
||||
<li class="oe_translatable_field">
|
||||
ERP translatable object data
|
||||
</li>
|
||||
<li class="oe_translatable_todo">
|
||||
Translation TODO
|
||||
Content to translate
|
||||
</li>
|
||||
<li class="oe_translatable_text">
|
||||
Translated content
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
In this mode, you can only translate texts. To
|
||||
change the structure of the page, you must edit the
|
||||
master page. Each modification on the master page
|
||||
is automatically applied to all translated
|
||||
versions.
|
||||
</p>
|
||||
</section>
|
||||
<hr/>
|
||||
<section class="row">
|
||||
|
|
|
@ -22,7 +22,19 @@
|
|||
<li><a data-action="show-mobile-preview" href="#"><span title="Mobile preview" class="icon-mobile-phone"/></a></li>
|
||||
<li class="divider-vertical"></li>
|
||||
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
|
||||
<li class="dropdown">
|
||||
<li class="dropdown js_hide_on_translate">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Content <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a data-action="edit-structure" href="#"><span title="Edit Top Menu">Edit Menu</span></a></li>
|
||||
<li class="divider"> </li>
|
||||
<li><a href="">New Page</a></li> <!-- Todo: Implement with inheritancy in each module-->
|
||||
<li><a href="">New Blog Post</a></li>
|
||||
<li><a href="">New Event</a></li>
|
||||
<li><a href="">New Job Offer</a></li>
|
||||
<li><a href="">New Product</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown js_hide_on_translate">
|
||||
<a id="customize-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Customize <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu" id="customize-menu">
|
||||
<!-- filled in JS -->
|
||||
|
@ -34,9 +46,6 @@
|
|||
<!-- filled in JS -->
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/web#action=website.action_module_website">Apps</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,4 +70,27 @@
|
|||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="website.error_dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button title="Close" type="button" class="close" data-dismiss="modal">×</button>
|
||||
<div class="h2"><t t-esc="title"/></div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<section>
|
||||
<t t-esc="message"/>
|
||||
</section>
|
||||
<section class="mt32">
|
||||
You have encountered an error. You can edit in the <a t-att-href="backend_url">backend</a> this data.
|
||||
</section>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" href="#" class="close btn btn-default">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import test_views, test_converter
|
||||
import test_views, test_converter, test_requests
|
||||
|
||||
checks = [ test_views, test_converter, ]
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unittest2
|
||||
|
||||
class URLCase(unittest2.TestCase):
|
||||
"""
|
||||
URLCase moved out of test_requests, otherwise discovery attempts to
|
||||
instantiate and run it
|
||||
"""
|
||||
def __init__(self, user, url, result):
|
||||
super(URLCase, self).__init__()
|
||||
self.user = user
|
||||
self.url = url
|
||||
self.result = result
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self.user or "Anonymous Coward"
|
||||
|
||||
def __str__(self):
|
||||
return "%s (as %s)" % (self.url, self.username)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def shortDescription(self):
|
||||
return ""
|
||||
|
||||
def runTest(self):
|
||||
code = self.result.getcode()
|
||||
self.assertIn(
|
||||
code, xrange(200, 300),
|
||||
"Fetching %s as %s returned an error response (%d)" % (
|
||||
self.url, self.username, code))
|
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import collections
|
||||
import urllib
|
||||
import urlparse
|
||||
import unittest2
|
||||
import urllib2
|
||||
|
||||
import lxml.html
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from . import cases
|
||||
|
||||
__all__ = ['load_tests', 'CrawlSuite']
|
||||
|
||||
class RedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
"""
|
||||
HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and
|
||||
works by intercepting 3xy "errors".
|
||||
|
||||
Inherit from it to handle 3xy non-error responses instead, as we're not
|
||||
using the error processor
|
||||
"""
|
||||
|
||||
def http_response(self, request, response):
|
||||
code, msg, hdrs = response.code, response.msg, response.info()
|
||||
|
||||
if 300 <= code < 400:
|
||||
return self.parent.error(
|
||||
'http', request, response, code, msg, hdrs)
|
||||
|
||||
return response
|
||||
|
||||
https_response = http_response
|
||||
|
||||
class CrawlSuite(unittest2.TestSuite):
|
||||
""" Test suite crawling an openerp CMS instance and checking that all
|
||||
internal links lead to a 200 response.
|
||||
|
||||
If a username and a password are provided, authenticates the user before
|
||||
starting the crawl
|
||||
"""
|
||||
|
||||
def __init__(self, user=None, password=None):
|
||||
super(CrawlSuite, self).__init__()
|
||||
|
||||
self.opener = urllib2.OpenerDirector()
|
||||
self.opener.add_handler(urllib2.UnknownHandler())
|
||||
self.opener.add_handler(urllib2.HTTPHandler())
|
||||
self.opener.add_handler(urllib2.HTTPSHandler())
|
||||
self.opener.add_handler(RedirectHandler())
|
||||
self.opener.add_handler(urllib2.HTTPCookieProcessor())
|
||||
|
||||
self._authenticate(user, password)
|
||||
self.user = user
|
||||
|
||||
def _request(self, path):
|
||||
return self.opener.open(urlparse.urlunsplit([
|
||||
'http', 'localhost:%s' % tools.config['xmlrpc_port'],
|
||||
path, '', ''
|
||||
]))
|
||||
|
||||
def _authenticate(self, user, password):
|
||||
# force tools.config['db_name'] in user session so opening `/` doesn't
|
||||
# blow up in multidb situations
|
||||
self.opener.open('http://localhost:{port}/web/?db={db}'.format(
|
||||
port=tools.config['xmlrpc_port'],
|
||||
db=urllib.quote_plus(tools.config['db_name']),
|
||||
))
|
||||
if user is not None:
|
||||
url = 'http://localhost:{port}/login?{query}'.format(
|
||||
port=tools.config['xmlrpc_port'],
|
||||
query=urllib.urlencode({
|
||||
'db': tools.config['db_name'],
|
||||
'login': user,
|
||||
'key': password,
|
||||
})
|
||||
)
|
||||
auth = self.opener.open(url)
|
||||
assert auth.getcode() < 400, "Auth failure %d" % auth.getcode()
|
||||
|
||||
def _wrapped_run(self, result, debug=False):
|
||||
paths = collections.deque(['/'])
|
||||
seen = set(paths)
|
||||
|
||||
while paths:
|
||||
url = paths.popleft()
|
||||
r = self._request(url)
|
||||
cases.URLCase(self.user, url, r).run(result)
|
||||
|
||||
if r.info().gettype() != 'text/html':
|
||||
continue
|
||||
|
||||
doc = lxml.html.fromstring(r.read())
|
||||
for link in doc.xpath('//a[@href]'):
|
||||
href = link.get('href')
|
||||
|
||||
# avoid repeats, even for links we won't crawl no need to
|
||||
# bother splitting them if we've already ignored them
|
||||
# previously
|
||||
if href in seen: continue
|
||||
seen.add(href)
|
||||
|
||||
parts = urlparse.urlsplit(href)
|
||||
|
||||
if parts.netloc or \
|
||||
not parts.path.startswith('/') or \
|
||||
parts.path == '/web' or\
|
||||
parts.path.startswith('/web/') or \
|
||||
(parts.scheme and parts.scheme not in ('http', 'https')):
|
||||
continue
|
||||
|
||||
paths.append(href)
|
||||
|
||||
def load_tests(loader, base, _):
|
||||
base.addTest(CrawlSuite())
|
||||
# blog duplicate (&al?) are on links
|
||||
base.addTest(CrawlSuite('admin', tools.config['admin_passwd']))
|
||||
base.addTest(CrawlSuite('demo', 'demo'))
|
||||
return base
|
|
@ -62,7 +62,7 @@
|
|||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_banner.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Banner</span>
|
||||
</div>
|
||||
<div id="myCarousel" class="oe_snippet_body carousel slide oe_medium mb32" contenteditable="false">
|
||||
<div id="myCarousel" class="oe_snippet_body carousel slide oe_medium mb32">
|
||||
<!-- Indicators -->
|
||||
<ol class="carousel-indicators hidden">
|
||||
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<div class="carousel-inner">
|
||||
<div class="item image_text active" style="background-image: url('/website/static/src/img/banner/color_splash.jpg')">
|
||||
<div class="container">
|
||||
<div class="carousel-caption content" contenteditable="true">
|
||||
<div class="carousel-caption content">
|
||||
<h2>Your Banner Title</h2>
|
||||
<h3>Click to customize this text</h3>
|
||||
<p>
|
||||
|
@ -78,7 +78,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="carousel-image hidden-xs">
|
||||
<img src="/website/static/src/img/banner/banner_picture.png" contenteditable="true" alt="Banner OpenERP Image"/>
|
||||
<img src="/website/static/src/img/banner/banner_picture.png" alt="Banner OpenERP Image"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -109,7 +109,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="col-md-6 mt16 mb16">
|
||||
<img class="img-responsive shadow" src="/website/static/src/img/text_image.png"/>
|
||||
<img class="img img-responsive shadow" src="/website/static/src/img/text_image.png"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -126,7 +126,7 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mt16 mb16">
|
||||
<img class="img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
|
||||
<img class="img img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-6 mt32">
|
||||
<h3>A Section Subtitle</h3>
|
||||
|
@ -243,7 +243,7 @@
|
|||
<h2>A Punchy Headline</h2>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<img class="img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
|
||||
<img class="img img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-offset-3 mb16 mt16">
|
||||
<p class="text-center">
|
||||
|
@ -277,7 +277,7 @@
|
|||
<h3 class="text-muted">And a good subtitle</h3>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<h4 class="mt16">Streamline Recruitments</h4>
|
||||
<p>
|
||||
Post job offers and keep track of each application
|
||||
|
@ -290,7 +290,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
|
||||
<img class="img img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
|
||||
<h4 class="mt16">Enterprise Social Network</h4>
|
||||
<p>
|
||||
Break down information silos. Share knowledge and best
|
||||
|
@ -302,7 +302,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
<img class="img img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
<h4 class="mt16">Leaves Management</h4>
|
||||
<p>
|
||||
Keep track of the vacation days accrued by each
|
||||
|
@ -323,6 +323,7 @@
|
|||
|
||||
<div data-snippet-id="well" data-selector-siblings="p, h1, h2, h3, blockquote">
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_well.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Well</span>
|
||||
</div>
|
||||
<div class="oe_snippet_body well">
|
||||
|
@ -381,19 +382,19 @@
|
|||
<h4 class="text-muted">More than 500 successful projects</h4>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -414,28 +415,28 @@
|
|||
<h4 class="text-muted">More than 500 successful projects</h4>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -554,7 +555,7 @@
|
|||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_button.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Button</span>
|
||||
</div>
|
||||
<section class="oe_snippet_body dark">
|
||||
<section class="oe_snippet_body oe_snippet_darken dark">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center mt16 mb16">
|
||||
|
@ -649,19 +650,19 @@
|
|||
</blockquote>
|
||||
</div>
|
||||
<div class="col-md-2 col-md-offset-1">
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
|
||||
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -690,9 +691,9 @@
|
|||
<ul class="dropdown-menu" name="parallax-background">
|
||||
<li data-value="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
|
||||
<li data-value="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
|
||||
<li data-value="/website/static/src/img/parallax/parallax_photo1.jpg"><a>Photo Woman</a></li>
|
||||
<li data-value="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
|
||||
<li class="oe_custom_bg"><a><b>Chose your picture</b></a></li>
|
||||
<li data-value="/website/static/src/img/parallax/parallax_bg.jpg"><a>Office</a></li>
|
||||
<li class="oe_custom_bg"><a><b>Choose your picture</b></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="oe_snippet_options dropdown-submenu">
|
||||
|
@ -706,16 +707,15 @@
|
|||
<li data-value="2"><a>Very Fast</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<div class="oe_snippet_body" style="position:relative">
|
||||
<div class="parallax oe_structure oe_small" style="background-image: url('/website/static/src/img/parallax/parallax_photo1.jpg')" data-stellar-background-ratio="0.3">
|
||||
|
||||
</div>
|
||||
<div class="oe_snippet_body parallax oe_small oe_structure"
|
||||
style="background-image: url('/website/static/src/img/parallax/parallax_bg.jpg')"
|
||||
data-scroll-background-ratio="0.6">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-snippet-id="parallax_quote" data-selector-children=".oe_structure, [data-oe-type=html]">
|
||||
<div class="oe_snippet_thumbnail">
|
||||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_parallax.png"/>
|
||||
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quotes_slider.png"/>
|
||||
<span class="oe_snippet_thumbnail_title">Quotes Slider</span>
|
||||
</div>
|
||||
<section class="oe_snippet_body parallax_quote oe_structure">
|
||||
|
|
|
@ -10,9 +10,27 @@
|
|||
<link id="website_css" rel='stylesheet' href='/website/static/src/css/website.css' t-ignore="true"/>
|
||||
</template>
|
||||
|
||||
<template id="layout" name="Main layout">
|
||||
<!DOCTYPE html>
|
||||
<template id="website.submenu" name="Submenu">
|
||||
<li t-if="not submenu.child_id">
|
||||
<a t-att-href="url_for(submenu.url)" t-ignore="true">
|
||||
<span t-field="submenu.name"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="submenu.child_id" class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<t t-foreach="submenu.child_id" t-as="submenu">
|
||||
<t t-call="website.submenu"/>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template id="layout" name="Main layout"><!DOCTYPE html>
|
||||
<html t-att-lang="lang.replace('_', '-')"
|
||||
t-att-data-website-id="website.id if editable else None"
|
||||
t-att-data-editable="'1' if editable else None"
|
||||
t-att-data-translatable="'1' if translatable else None"
|
||||
t-att-data-view-xmlid="xmlid if editable else None"
|
||||
|
@ -49,11 +67,15 @@
|
|||
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Bootstrap and jQuery UI conflicts
|
||||
$.fn.bstooltip = $.fn.tooltip;
|
||||
$.fn.bsbutton = $.fn.button;
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/website/static/lib/stellar/jquery.stellar.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.js"></script>
|
||||
|
||||
<t t-if="editable">
|
||||
|
@ -65,11 +87,13 @@
|
|||
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
|
||||
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
|
||||
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
|
||||
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
|
||||
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
|
||||
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
|
||||
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
|
||||
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
|
||||
|
@ -93,38 +117,22 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" t-href="/page/website.homepage"><em>Your</em><b>Company</b></a>
|
||||
<a class="navbar-brand" href="/">Your<b>Company</b></a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-top-collapse">
|
||||
<ul class="nav navbar-nav navbar-right" id="top_menu">
|
||||
<li name="contactus"><a t-href="/page/website.contactus">Contact us</a></li>
|
||||
<li t-if="user_id.id == website.public_user.id"><a href="/web">Sign in</a></li>
|
||||
<li t-if="user_id.id != website.public_user.id"><a href="/web"><span t-field="user_id.name"/></a></li>
|
||||
<li t-if="request.multilang and
|
||||
(len(website.language_ids) > 1 or editable)" class="dropdown">
|
||||
<!-- TODO: use flags for language selection -->
|
||||
<a class="dropdown-toggle js_language_selected" data-toggle="dropdown" href="#">
|
||||
<t t-esc="lang_selected[0]['name'].split('/').pop()"/> <span class="caret"></span>
|
||||
<t t-foreach="website.get_menu().child_id" t-as="submenu">
|
||||
<t t-call="website.submenu"/>
|
||||
</t>
|
||||
<li class="active dropdown" t-ignore="true" t-if="user_id.id != website.public_user.id">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<span t-esc="user_id.name"/>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu js_language_selector" role="menu">
|
||||
<li t-foreach="website.language_ids" t-as="lg">
|
||||
<a t-att-href="url_for('', lang=lg.code)" role="menuitem"
|
||||
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
|
||||
<strong t-att-class="'icon-circle' if lg.code == lang
|
||||
else 'icon-circle-blank'"></strong>
|
||||
<t t-esc="lg.name.split('/').pop()"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="editable" class="divider"/>
|
||||
<li t-if="editable">
|
||||
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
|
||||
<t t-if="request.httprequest.query_string">
|
||||
<t t-set="url_return" t-value="url_return + '?' + request.httprequest.query_string"/>
|
||||
</t>
|
||||
<a t-attf-href="/web#action=base.action_view_base_language_install&website_id=#{website.id}&url_return=#{url_return}">
|
||||
Add a language...
|
||||
</a>
|
||||
</li>
|
||||
<ul class="dropdown-menu js_usermenu" role="menu">
|
||||
<li><a href="/web" role="menuitem">Administration</a></li>
|
||||
<li class="divider"/>
|
||||
<li><a t-attf-href="/web/session/logout?redirect=#{ url_for('') }" role="menuitem">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -161,24 +169,42 @@
|
|||
</h2>
|
||||
</div>
|
||||
<div class="col-md-5 col-lg-offset-1" name="about_us">
|
||||
<h4>
|
||||
<span t-field="res_company.name">Your Company</span>
|
||||
<small> - <a href="/page/website.aboutus">About us</a></small>
|
||||
</h4>
|
||||
<p>
|
||||
We are a team of passionated people whose goal is to improve everyone's
|
||||
life through disruptive products. We build great products to solve your
|
||||
business problems.
|
||||
</p>
|
||||
<p>
|
||||
Our products are designed for small to medium companies willing to optimize
|
||||
their performance.
|
||||
</p>
|
||||
<div>
|
||||
<h4>
|
||||
<span t-field="res_company.name">Your Company</span>
|
||||
<small> - <a href="/page/website.aboutus">About us</a></small>
|
||||
</h4>
|
||||
<p>
|
||||
We are a team of passionated people whose goal is to improve everyone's
|
||||
life through disruptive products. We build great products to solve your
|
||||
business problems.
|
||||
</p>
|
||||
<p>
|
||||
Our products are designed for small to medium companies willing to optimize
|
||||
their performance.
|
||||
</p>
|
||||
</div>
|
||||
<ul class="nav nav-pills js_language_selector" t-if="request.multilang and
|
||||
(len(website.language_ids) > 1 or editable)">
|
||||
<li t-foreach="website.language_ids" t-as="lg">
|
||||
<a t-att-href="url_for('', lang=lg.code)"
|
||||
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
|
||||
<t t-esc="lg.name.split('/').pop()"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="editable">
|
||||
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
|
||||
<a t-attf-href="/web#action=base.action_view_base_language_install&website_id=#{website.id}&url_return=#{url_return}">
|
||||
<i class="icon-plus-sign"/>
|
||||
Add a language...
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt16">
|
||||
<div class="pull-right" t-ignore="1">
|
||||
<div class="pull-right" t-ignore="true" t-if="not editable">
|
||||
Create a <a href="http://openerp.com/apps/website">free website</a> with
|
||||
<a class="label label-danger" href="https://openerp.com/apps/website">OpenERP</a>
|
||||
</div>
|
||||
|
@ -192,32 +218,44 @@
|
|||
</html>
|
||||
</template>
|
||||
|
||||
<template id="show_sign_in" inherit_option_id="website.layout" inherit_id="website.layout" name="Show Sign In">
|
||||
<xpath expr="//ul[@id='top_menu']" position="inside">
|
||||
<li class="active" t-if="user_id.id == website.public_user.id">
|
||||
<a t-attf-href="/web#redirect=#{ url_for('') }">
|
||||
Sign in
|
||||
</a>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="footer_custom" inherit_option_id="website.layout" name="Custom Footer">
|
||||
<xpath expr="//div[@id='footer_container']" position="before">
|
||||
<section data-snippet-id='three-columns' class="mt16 mb16">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle</h4>
|
||||
<p>
|
||||
<a t-href="/">Homepage</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle 2</h4>
|
||||
<p>
|
||||
...
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle 3</h4>
|
||||
<p>
|
||||
...
|
||||
</p>
|
||||
<div class="oe_structure">
|
||||
<section data-snippet-id='three-columns' class="mt16 mb16">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle</h4>
|
||||
<p>
|
||||
<a href="/">Homepage</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle 2</h4>
|
||||
<p>
|
||||
...
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="mt16">Subtitle 3</h4>
|
||||
<p>
|
||||
...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='footer_container']" position="attributes">
|
||||
<attribute name="style">display: none</attribute>
|
||||
|
@ -225,9 +263,8 @@
|
|||
</template>
|
||||
|
||||
<template id="publish_management">
|
||||
<t t-if="editable" t-ignore="true">
|
||||
<div class="pull-right">
|
||||
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name">
|
||||
<div t-if="editable" t-ignore="true" class="pull-right hidden-xs" t-att-style="style or ''">
|
||||
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
|
||||
<a t-attf-class="btn btn-sm btn-#{object.id and object.website_published and 'success' or 'default'}" t-att-id="'dopprod-%s' % object.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % object.id">
|
||||
<t t-raw="0"/>
|
||||
|
@ -235,9 +272,6 @@
|
|||
<a href="#" class="js_publish_btn css_unpublish">Unpublish</a>
|
||||
<a href="#" class="js_publish_btn css_publish">Publish</a>
|
||||
</li>
|
||||
<li t-if="publish_duplicate">
|
||||
<a t-att-href="publish_duplicate">Duplicate</a>
|
||||
</li>
|
||||
<li t-if="publish_edit">
|
||||
<a t-att-href="'/web#model=%s&id=%s' % (object._name, object.id)"
|
||||
title='Edit in backend'>Edit</a>
|
||||
|
@ -245,7 +279,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="publish_short">
|
||||
|
@ -262,7 +295,7 @@
|
|||
</template>
|
||||
|
||||
<template id="pager" name="Pager">
|
||||
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
|
||||
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination" t-att-style="style or ''">
|
||||
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
|
||||
<a t-att-href=" pager['page_previous']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
|
||||
</li>
|
||||
|
@ -364,8 +397,8 @@
|
|||
<h1 class="container mt32">500: Internal Server Error!</h1>
|
||||
</div>
|
||||
<t t-if="editable">
|
||||
<h3>An exception appear in the template: <span id="exception_template" t-esc="template"/></h3>
|
||||
<b id="exception_message" t-esc="message"/>
|
||||
<h3>Exception in template: <span id="exception_template" t-esc="template"/></h3>
|
||||
<h4 t-if="expr">Expression: <t t-esc="expr"/></h4>
|
||||
<pre id="exception_node" t-esc="node"/>
|
||||
<pre id="exception_traceback" t-esc="traceback"/>
|
||||
</t>
|
||||
|
@ -401,10 +434,22 @@ User-agent: *
|
|||
Sitemap: <t t-esc="url_root"/>sitemap.xml
|
||||
</template>
|
||||
|
||||
<template id="sitemap">
|
||||
<template id="sitemap" name="Site Map">
|
||||
<t t-call="website.layout">
|
||||
<ul>
|
||||
<li t-foreach="pages" t-as="page">
|
||||
<a t-att-href="page['url']"><t t-esc="page['name']"/></a>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="sitemap_xml">
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<t t-foreach="pages" t-as="page">
|
||||
<url t-esc="page['url']"/>
|
||||
<url>
|
||||
<loc><t t-esc="page['url']"/></loc>
|
||||
</url>
|
||||
</t>
|
||||
</urlset>
|
||||
</template>
|
||||
|
@ -427,8 +472,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||
<span>&#x2706; <span t-field="res_company.phone"></span></span><br />
|
||||
<i class="icon-envelope"></i> <span t-field="res_company.email"></span>
|
||||
</address>
|
||||
<a t-att-href="res_company.partner_id.google_map_link()" target="_BLANK">
|
||||
<img class="thumbnail img-responsive" t-att-src="res_company.partner_id.google_map_img()" />
|
||||
<a t-att-href="res_company.google_map_link()" target="_BLANK">
|
||||
<img class="thumbnail img-responsive" t-att-src="res_company.google_map_img()" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
|
@ -460,31 +505,65 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
|||
|
||||
<template id="aboutus" name="About us" page="True">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="oe_structure"/>
|
||||
<div class="container mb32">
|
||||
<div class="row col-wrap" id="aboutus">
|
||||
<div class="col-sm-12 text-center">
|
||||
<h1>About us</h1>
|
||||
<h3 class="text-muted">A passion for great products</h3>
|
||||
</div>
|
||||
<div class="col-sm-8 mt16">
|
||||
<p>
|
||||
We are a team of passionated people whose goal is to improve everyone's
|
||||
life through disruptive products. We build great products to solve your
|
||||
business problems.
|
||||
</p>
|
||||
<p>
|
||||
Our products are designed for small to medium companies willing to optimize
|
||||
their performance.
|
||||
</p>
|
||||
</div><div class="col-sm-3 col-sm-offset-1">
|
||||
<img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Out Team"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
<div id="wrap" class="oe_structure">
|
||||
|
||||
<section data-snippet-id="title">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center">About us</h1>
|
||||
<h3 class="text-muted text-center">Great products for great people</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-snippet-id="text-image">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mt32">
|
||||
<p>
|
||||
We are a team of passionated people whose goal is to improve everyone's
|
||||
life through disruptive products. We build great products to solve your
|
||||
business problems.
|
||||
</p>
|
||||
<p>
|
||||
Our products are designed for small to medium companies willing to optimize
|
||||
their performance.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2 mt16 mb16">
|
||||
<img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Our Team"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="parallax oe_structure mt16 oe_medium mb64" data-scroll-background-offset="0" data-scroll-background-ratio="1" data-snippet-id="parallax" style="background-image: url(http://localhost:8069/website/static/src/img/parallax/parallax_bg.jpg); background-attachment: scroll; background-position: 0px 0px; ">
|
||||
<section class="mb32 mt16" data-snippet-id="references">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mt16 mb8">
|
||||
<h1 class="text-center">What do customers say about us...</h1>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-1 mt16 mb0">
|
||||
<blockquote data-snippet-id="quote">
|
||||
<p><span style="background-color:#FFFFFF;">Write here a quote from one of your customer. Quotes are are great way to give confidence in your products or services.</span></p>
|
||||
<small><span style="background-color:#FFFFFF;">Author of this quote</span></small>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-2 mt16 mb32">
|
||||
<blockquote data-snippet-id="quote">
|
||||
<p><span style="background-color:#FFFFFF;">OpenERP provides essential platform for our project management. Things are better organized and more visible with it.</span></p>
|
||||
<small><span style="background-color:#FFFFFF;">John Doe, CEO</span></small>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -45,9 +45,6 @@
|
|||
</div>
|
||||
<group>
|
||||
<group string="Social Icons">
|
||||
<p class="oe_grey" colspan="2">
|
||||
Keep these fields empty to not show the related social icon.
|
||||
</p>
|
||||
<field name="social_twitter" placeholder="https://twitter.com/openerp"/>
|
||||
<field name="social_facebook" placeholder="https://facebook.com/openerp"/>
|
||||
<field name="social_googleplus" placeholder="https://plus.google.com/+openerp"/>
|
||||
|
|
|
@ -35,24 +35,23 @@ class WebsiteBlog(http.Controller):
|
|||
|
||||
@website.route([
|
||||
'/blog/',
|
||||
'/blog/<int:blog_post_id>/',
|
||||
'/blog/<int:blog_post_id>/page/<int:page>/',
|
||||
'/blog/cat/<int:category_id>/',
|
||||
'/blog/cat/<int:category_id>/page/<int:page>/',
|
||||
'/blog/tag/',
|
||||
'/blog/tag/<int:tag_id>/',
|
||||
], type='http', auth="public")
|
||||
def blog(self, category_id=None, blog_post_id=None, tag_id=None, page=1, **post):
|
||||
'/blog/page/<int:page>/',
|
||||
'/blog/<model("blog.post"):blog_post>/',
|
||||
'/blog/<model("blog.post"):blog_post>/page/<int:page>/',
|
||||
'/blog/cat/<model("blog.category"):category>/',
|
||||
'/blog/cat/<model("blog.category"):category>/page/<int:page>/',
|
||||
'/blog/tag/<model("blog.tag"):tag>/',
|
||||
'/blog/tag/<model("blog.tag"):tag>/page/<int:page>/',
|
||||
], type='http', auth="public", multilang=True)
|
||||
def blog(self, category=None, blog_post=None, tag=None, page=1, enable_editor=None):
|
||||
""" Prepare all values to display the blog.
|
||||
|
||||
:param integer category_id: id of the category currently browsed.
|
||||
:param integer tag_id: id of the tag that is currently used to filter
|
||||
blog posts
|
||||
:param integer blog_post_id: ID of the blog post currently browsed. If not
|
||||
set, the user is browsing the category and
|
||||
a post pager is calculated. If set the user
|
||||
is reading the blog post and a comments pager
|
||||
is calculated.
|
||||
:param category: category currently browsed.
|
||||
:param tag: tag that is currently used to filter blog posts
|
||||
:param blog_post: blog post currently browsed. If not set, the user is
|
||||
browsing the category and a post pager is calculated.
|
||||
If set the user is reading the blog post and a
|
||||
comments pager is calculated.
|
||||
:param integer page: current page of the pager. Can be the category or
|
||||
post pager.
|
||||
:param dict post: kwargs, may contain
|
||||
|
@ -73,44 +72,39 @@ class WebsiteBlog(http.Controller):
|
|||
"""
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
blog_post_obj = request.registry['blog.post']
|
||||
tag_obj = request.registry['blog.tag']
|
||||
category_obj = request.registry['blog.category']
|
||||
|
||||
tag = None
|
||||
category = None
|
||||
blog_post = None
|
||||
blog_posts = None
|
||||
pager = None
|
||||
nav = {}
|
||||
|
||||
category_ids = category_obj.search(cr, uid, [], context=context)
|
||||
categories = category_obj.browse(cr, uid, category_ids, context=context)
|
||||
|
||||
if tag_id:
|
||||
tag = tag_obj.browse(cr, uid, tag_id, context=context)
|
||||
if category_id:
|
||||
category = category_obj.browse(cr, uid, category_id, context=context)
|
||||
elif blog_post_id:
|
||||
blog_post = blog_post_obj.browse(cr, uid, blog_post_id, context=context)
|
||||
blog_message_ids = blog_post.website_message_ids
|
||||
if blog_post:
|
||||
category = blog_post.category_id
|
||||
category_id = category.id
|
||||
|
||||
if not blog_post_id:
|
||||
if category and tag:
|
||||
blog_posts = [cat_post for cat_post in category.blog_post_ids
|
||||
if tag_id in [post_tag.id for post_tag in cat_post.tag_ids]]
|
||||
elif category:
|
||||
pager = request.website.pager(
|
||||
url="/blog/%s/" % blog_post.id,
|
||||
total=len(blog_post.website_message_ids),
|
||||
page=page,
|
||||
step=self._post_comment_per_page,
|
||||
scope=7
|
||||
)
|
||||
pager_begin = (page - 1) * self._post_comment_per_page
|
||||
pager_end = page * self._post_comment_per_page
|
||||
blog_post.website_message_ids = blog_post.website_message_ids[pager_begin:pager_end]
|
||||
else:
|
||||
if category:
|
||||
pager_url = "/blog/cat/%s/" % category.id
|
||||
blog_posts = category.blog_post_ids
|
||||
elif tag:
|
||||
pager_url = '/blog/tag/%s/' % tag.id
|
||||
blog_posts = tag.blog_post_ids
|
||||
else:
|
||||
pager_url = '/blog/'
|
||||
blog_post_ids = blog_post_obj.search(cr, uid, [], context=context)
|
||||
blog_posts = blog_post_obj.browse(cr, uid, blog_post_ids, context=context)
|
||||
|
||||
if blog_posts:
|
||||
pager = request.website.pager(
|
||||
url="/blog/cat/%s/" % category_id,
|
||||
url=pager_url,
|
||||
total=len(blog_posts),
|
||||
page=page,
|
||||
step=self._category_post_per_page,
|
||||
|
@ -120,19 +114,9 @@ class WebsiteBlog(http.Controller):
|
|||
pager_end = page * self._category_post_per_page
|
||||
blog_posts = blog_posts[pager_begin:pager_end]
|
||||
|
||||
if blog_post:
|
||||
pager = request.website.pager(
|
||||
url="/blog/%s/" % blog_post_id,
|
||||
total=len(blog_message_ids),
|
||||
page=page,
|
||||
step=self._post_comment_per_page,
|
||||
scope=7
|
||||
)
|
||||
pager_begin = (page - 1) * self._post_comment_per_page
|
||||
pager_end = page * self._post_comment_per_page
|
||||
blog_post.website_message_ids = blog_post.website_message_ids[pager_begin:pager_end]
|
||||
|
||||
nav = {}
|
||||
for group in blog_post_obj.read_group(cr, uid, [], ['name', 'create_date'], groupby="create_date", orderby="create_date asc", context=context):
|
||||
# FIXME: vietnamese month names contain spaces. Set sail for fail.
|
||||
year = group['create_date'].split(" ")[1]
|
||||
if not year in nav:
|
||||
nav[year] = {'name': year, 'create_date_count': 0, 'months': []}
|
||||
|
@ -147,7 +131,7 @@ class WebsiteBlog(http.Controller):
|
|||
'blog_posts': blog_posts,
|
||||
'pager': pager,
|
||||
'nav_list': nav,
|
||||
'enable_editor': post.get('enable_editor')
|
||||
'enable_editor': enable_editor,
|
||||
}
|
||||
|
||||
if blog_post:
|
||||
|
@ -159,7 +143,9 @@ class WebsiteBlog(http.Controller):
|
|||
|
||||
return request.website.render("website_blog.index", values)
|
||||
|
||||
@website.route(['/blog/nav'], type='http', auth="public")
|
||||
# TODO: Refactor (used in website_blog.js for archive links)
|
||||
# => the archive links should be generated server side
|
||||
@website.route(['/blog/nav'], type='http', auth="public", multilang=True)
|
||||
def nav(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
blog_post_ids = request.registry['blog.post'].search(
|
||||
|
@ -171,32 +157,25 @@ class WebsiteBlog(http.Controller):
|
|||
blog_post_data = [
|
||||
{
|
||||
'id': blog_post.id,
|
||||
'name': blog_post.name,
|
||||
'website_published': blog_post.website_published,
|
||||
'category_id': blog_post.category_id and blog_post.category_id.id or False,
|
||||
'fragment': request.website.render("website_blog.blog_archive_link", {
|
||||
'blog_post': blog_post
|
||||
}),
|
||||
}
|
||||
for blog_post in request.registry['blog.post'].browse(cr, uid, blog_post_ids, context=context)
|
||||
]
|
||||
return simplejson.dumps(blog_post_data)
|
||||
|
||||
@website.route(['/blog/<int:blog_post_id>/post'], type='http', auth="public")
|
||||
@website.route(['/blog/<int:blog_post_id>/comment'], type='http', auth="public")
|
||||
def blog_post_comment(self, blog_post_id=None, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
url = request.httprequest.host_url
|
||||
request.session.body = post.get('body')
|
||||
if request.context['is_public_user']: # purpose of this ?
|
||||
return '%s/web#action=redirect&url=%s/blog/%s/post' % (url, url, blog_post_id)
|
||||
|
||||
if request.session.get('body') and blog_post_id:
|
||||
request.registry['blog.post'].message_post(
|
||||
cr, uid, blog_post_id,
|
||||
body=request.session.body,
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
request.session.body = False
|
||||
|
||||
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (blog_post_id))
|
||||
request.registry['blog.post'].message_post(
|
||||
cr, uid, blog_post_id,
|
||||
body=post.get('comment'),
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
|
||||
|
||||
@website.route(['/blog/<int:category_id>/new'], type='http', auth="public")
|
||||
def blog_post_create(self, category_id=None, **post):
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<!-- <data noupdate="1"> -->
|
||||
<data noupdate="1">
|
||||
<record id="menu_blog" model="website.menu">
|
||||
<field name="name">News</field>
|
||||
<field name="url">/blog</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">50</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<data>
|
||||
|
||||
<!-- jump to blog at install -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import url(compass/css3.css);
|
||||
@charset "UTF-8";
|
||||
.css_website_mail .has-error {
|
||||
border-color: red;
|
||||
}
|
||||
|
@ -22,11 +22,14 @@ p.post-meta {
|
|||
}
|
||||
|
||||
.js_website_blog div#right_column section {
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.js_website_blog div#right_column section:hover {
|
||||
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
opacity: 1;
|
||||
-moz-transition: all 0.2s ease-out;
|
||||
-webkit-transition: all 0.2s ease-out;
|
||||
-moz-transition: all 0.2s ease-out;
|
||||
-o-transition: all 0.2s ease-out;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "compass/css3"
|
||||
|
||||
.css_website_mail
|
||||
.has-error
|
||||
border-color: red
|
||||
.css_nav_month
|
||||
display: none
|
||||
&:first-of-type
|
||||
display: block
|
||||
.has-error
|
||||
border-color: red
|
||||
.css_nav_month
|
||||
display: none
|
||||
&:first-of-type
|
||||
display: block
|
||||
|
||||
.blog_content
|
||||
a.oe_mail_expand:after
|
||||
content: " →"
|
||||
a.oe_mail_expand
|
||||
font-weight: bold
|
||||
a.oe_mail_expand:after
|
||||
content: " →"
|
||||
a.oe_mail_expand
|
||||
font-weight: bold
|
||||
|
||||
p.post-meta
|
||||
position: relative
|
||||
top: -5px
|
||||
position: relative
|
||||
top: -5px
|
||||
|
||||
.js_website_blog
|
||||
div#right_column
|
||||
section
|
||||
opacity: 0.6
|
||||
section:hover
|
||||
opacity: 1
|
||||
-moz-transition: all 0.2s ease-out
|
||||
-webkit-transition: all 0.2s ease-out
|
||||
transition: all 0.2s ease-out
|
||||
|
||||
div#right_column
|
||||
section
|
||||
+opacity(0.6)
|
||||
section:hover
|
||||
+opacity(1)
|
||||
@include transition(all 0.2s ease-out)
|
||||
|
|
|
@ -8,15 +8,14 @@ $(document).ready(function () {
|
|||
e.preventDefault();
|
||||
var $ul = $(this).next("ul");
|
||||
if (!$ul.find('li').length) {
|
||||
// TODO: Why POST? (to pass the domain) A GET would be more appropriate...
|
||||
// This should be done server side anyway...
|
||||
$.post('/blog/nav', {'domain': $(this).data("domain")}, function (result) {
|
||||
var blog_id = +window.location.pathname.split("/").pop();
|
||||
$(JSON.parse(result)).each(function () {
|
||||
var $a = $('<a href="/blog/' + this.id + '"/>').text(this.name);
|
||||
var $li = $("<li/>").append($a);
|
||||
if (blog_id == this.id)
|
||||
$li.addClass("active");
|
||||
if (!this.website_published)
|
||||
$a.css("color", "red");
|
||||
var $li = $($.parseHTML(this.fragment));
|
||||
if (blog_id == this.id) $li.addClass("active");
|
||||
if (!this.website_published) $li.find('a').css("color", "red");
|
||||
$ul.append($li);
|
||||
});
|
||||
|
||||
|
@ -26,16 +25,4 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
var $form = $('.js_website_blog form#comment');
|
||||
$form.submit(function (e) {
|
||||
e.preventDefault();
|
||||
var error = $form.find("textarea").val().length < 3;
|
||||
$form.find("textarea").toggleClass("has-error", error);
|
||||
if (!error) {
|
||||
$form.css("visibility", "hidden");
|
||||
$.post(window.location.pathname + '/post', {'body': $form.find("textarea").val()}, function (url) {
|
||||
window.location.href = url
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,18 +2,9 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="header_footer_custom" inherit_id="website.layout">
|
||||
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
|
||||
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
|
||||
</xpath>
|
||||
<template id="header_footer_custom" inherit_id="website.layout" name="Footer News Blog Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="footer_custom" inherit_option_id="website.layout" name="Job Announces">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a t-href="/blog/%(website_blog.blog_category_2)d/">Jobs</a></li>
|
||||
<li><a t-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -24,17 +15,24 @@
|
|||
<t t-call="website.publish_management">
|
||||
<t t-set="object" t-value="blog_post"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
|
||||
<li>
|
||||
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
|
||||
<script>
|
||||
var $a=$("[data-href$='/duplicate']");
|
||||
$a.attr("href", $a.data('href')).removeAttr('data-href');
|
||||
$a.next("script").remove();
|
||||
</script>
|
||||
</li>
|
||||
</t>
|
||||
</div>
|
||||
<h2 class="text-center">
|
||||
<a t-attf-href="/blog/#{blog_post.id}" t-field="blog_post.name"></a>
|
||||
<a t-href="/blog/#{blog_post.id}" t-field="blog_post.name"></a>
|
||||
</h2>
|
||||
<p class="post-meta text-muted text-center" name='blog_post_data'>
|
||||
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &nbsp;
|
||||
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &nbsp;</span>
|
||||
<span t-if="len(blog_post.message_ids) > 0" class="icon-comment">
|
||||
<a t-attf-href="/blog/#{blog_post.id}/#comment">
|
||||
<a t-href="/blog/#{blog_post.id}/#comment">
|
||||
<t t-if="len(blog_post.message_ids) <= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
|
||||
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
|
||||
</a>
|
||||
|
@ -69,7 +67,7 @@
|
|||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="icon-tags"/>
|
||||
<t t-foreach="blog_post.tag_ids" t-as="tag">
|
||||
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &nbsp;
|
||||
<a t-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &nbsp;
|
||||
</t>
|
||||
</p>
|
||||
</xpath>
|
||||
|
@ -81,7 +79,14 @@
|
|||
<t t-call="website.publish_management">
|
||||
<t t-set="object" t-value="blog_post"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
|
||||
<li>
|
||||
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
|
||||
<script>
|
||||
var $a=$("[data-href$='/duplicate']");
|
||||
$a.attr("href", $a.data('href')).removeAttr('data-href');
|
||||
$a.next("script").remove();
|
||||
</script>
|
||||
</li>
|
||||
</t>
|
||||
</div>
|
||||
<div class="clearfix"/>
|
||||
|
@ -90,7 +95,7 @@
|
|||
<p class="post-meta text-muted text-center" name="blog_post_data">
|
||||
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &nbsp;
|
||||
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &nbsp;</span>
|
||||
<span t-if="len(blog_post.message_ids) > 0" class="icon-comment"> With
|
||||
<span t-if="len(blog_post.message_ids) > 0" class="icon-comment"> With
|
||||
<a t-attf-href="#comments">
|
||||
<t t-if="len(blog_post.message_ids) <= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
|
||||
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
|
||||
|
@ -132,17 +137,19 @@
|
|||
<template id="opt_blog_post_complete_comment" name="Comment Form"
|
||||
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
|
||||
<xpath expr="//ul[last()]" position="after">
|
||||
<section groups="group_website_blog_reply" class="mb32">
|
||||
<h4>Leave a Comment</h4>
|
||||
<form id="comment" t-attf-action="/blog/#{blog_post.id}/post#post"
|
||||
method="POST">
|
||||
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&field=image_small&id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
|
||||
<div class="pull-left mb32" style="width: 75%%">
|
||||
<textarea rows="3" class="form-control" placeholder="Write a comment..."></textarea>
|
||||
<button type="submit" class="btn btn-danger mt8">Post</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<t t-if="not is_public_user">
|
||||
<section groups="group_website_blog_reply" class="mb32">
|
||||
<h4>Leave a Comment</h4>
|
||||
<form id="comment" t-attf-action="/blog/#{blog_post.id}/comment"
|
||||
method="POST">
|
||||
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&field=image_small&id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
|
||||
<div class="pull-left mb32" style="width: 75%%">
|
||||
<textarea rows="3" name="comment" class="form-control" placeholder="Write a comment..."></textarea>
|
||||
<button type="submit" class="btn btn-danger mt8">Post</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
@ -169,14 +176,14 @@
|
|||
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
|
||||
<span class="icon-tags"/>
|
||||
<t t-foreach="blog_post.tag_ids" t-as="tag">
|
||||
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &nbsp;
|
||||
<a t-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &nbsp;
|
||||
</t>
|
||||
</p>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Page -->
|
||||
<template id="index" name="Blog" page="True">
|
||||
<!-- Page -->
|
||||
<template id="index" name="Blog">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
|
||||
|
@ -187,8 +194,8 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12 col-sm-12" t-if="not blog_post" id="blog_post">
|
||||
<t t-if="category and editable">
|
||||
<div class="row">
|
||||
<a t-attf-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
|
||||
<div class="row hidden-xs">
|
||||
<a t-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="blog_posts" t-as="blog_post" data-publish="">
|
||||
|
@ -301,7 +308,7 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
<t t-foreach="categories" t-as="nav_category">
|
||||
<li>
|
||||
<a t-attf-href="/blog/#{nav_category.id}">
|
||||
<a t-href="/blog/#{nav_category.id}">
|
||||
<span t-field="nav_category.name"/>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -311,5 +318,11 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="blog_archive_link">
|
||||
<li>
|
||||
<a t-href="/blog/#{blog_post.id}"><t t-esc="blog_post.name"/></a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="contactus_thanks" name="Contact us" page="True">
|
||||
<template id="contactus_thanks" name="Contact us">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="oe_structure"/>
|
||||
|
|
|
@ -28,7 +28,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
|
|||
if request.context['is_public_user']:
|
||||
base_partner_domain += [('website_published', '=', True)]
|
||||
partner_domain = list(base_partner_domain)
|
||||
if grade_id:
|
||||
if grade_id and grade_id != "all":
|
||||
partner_domain += [('grade_id', '=', int(grade_id))] # try/catch int
|
||||
if country_id:
|
||||
country = country_obj.browse(request.cr, request.uid, country_id, request.context)
|
||||
|
@ -94,7 +94,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
|
|||
def partners_ref(self, partner_id=0, **post):
|
||||
partner_obj = request.registry['res.partner']
|
||||
if request.context['is_public_user']:
|
||||
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_pushished', '=', True), ('id', '=', partner_id)], context=request.context)
|
||||
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_published', '=', True), ('id', '=', partner_id)], context=request.context)
|
||||
else:
|
||||
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
|
||||
if not partner_ids:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Partners Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a t-href="/partners/">Partners</a></li>
|
||||
</xpath>
|
||||
|
|
|
@ -14,7 +14,7 @@ class WebsiteCustomer(http.Controller):
|
|||
@website.route([
|
||||
'/customers/', '/customers/page/<int:page>/',
|
||||
'/customers/country/<int:country_id>', '/customers/country/<int:country_id>/page/<int:page>/'
|
||||
], type='http', auth="public")
|
||||
], type='http', auth="public", multilang=True)
|
||||
def customers(self, country_id=None, page=0, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
partner_obj = request.registry['res.partner']
|
||||
|
@ -70,7 +70,7 @@ class WebsiteCustomer(http.Controller):
|
|||
}
|
||||
return request.website.render("website_customer.index", values)
|
||||
|
||||
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public")
|
||||
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public", multilang=True)
|
||||
def customer(self, partner_id=None, **post):
|
||||
""" Route for displaying a single partner / customer.
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<data>
|
||||
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Customer References Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a href="/customers/">Customer References</a></li>
|
||||
<li><a t-href="/customers/">Customer References</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Page -->
|
||||
<!-- Page -->
|
||||
<template id="layout" name="Customer References Layout">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="additional_title">Customer References</t>
|
||||
|
@ -53,7 +53,7 @@
|
|||
<t t-set="classname" t-value="'pull-left'"/>
|
||||
</t>
|
||||
<form action="/customers/" method="get" class="navbar-search pull-right pagination form-inline">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<input type="text" name="search" class="search-query form-control"
|
||||
placeholder="Search" t-att-value="post.get('search', '')"/>
|
||||
</div>
|
||||
|
@ -63,11 +63,11 @@
|
|||
<div>
|
||||
<div t-foreach="partner_ids" t-as="partner" class="media thumbnail" data-publish="">
|
||||
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
|
||||
<a class="pull-left" t-attf-href="/customers/#{ partner.id }/">
|
||||
<a class="pull-left" t-href="/customers/#{ partner.id }/">
|
||||
<img class="media-object" t-att-src="partner.img('image_small')"/>
|
||||
</a>
|
||||
<div class="media-body" style="min-height: 64px;">
|
||||
<a class="media-heading" t-attf-href="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
|
||||
<a class="media-heading" t-href="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
|
||||
<div t-field="partner.website_short_description"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@ Online Events
|
|||
'data': [
|
||||
'data/event_data.xml',
|
||||
'views/website_event.xml',
|
||||
'views/website_event_sale_backend.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/website_event.xml',
|
||||
],
|
||||
|
|
|
@ -25,6 +25,9 @@ from openerp.addons.web.http import request
|
|||
from openerp.tools.translate import _
|
||||
from openerp.addons import website_sale
|
||||
from openerp.addons.website.models import website
|
||||
from openerp.addons.website.controllers.main import Website as controllers
|
||||
controllers = controllers()
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
@ -76,13 +79,14 @@ class website_event(http.Controller):
|
|||
]
|
||||
|
||||
# search domains
|
||||
current_date = dates[0][1]
|
||||
current_date = None
|
||||
current_type = None
|
||||
current_country = None
|
||||
for date in dates:
|
||||
if searches["date"] == date[0]:
|
||||
domain_search["date"] = date[2]
|
||||
current_date = date[1]
|
||||
if date[0] != 'all':
|
||||
current_date = date[1]
|
||||
if searches["type"] != 'all':
|
||||
current_type = type_obj.browse(cr, uid, int(searches['type']), context=context)
|
||||
domain_search["type"] = [("type", "=", int(searches["type"]))]
|
||||
|
@ -182,7 +186,10 @@ class website_event(http.Controller):
|
|||
|
||||
_values = None
|
||||
for key, value in post.items():
|
||||
quantity = int(value)
|
||||
try:
|
||||
quantity = int(value)
|
||||
except:
|
||||
quantity = None
|
||||
ticket_id = key.split("-")[0] == 'ticket' and int(key.split("-")[1]) or None
|
||||
if not ticket_id or not quantity:
|
||||
continue
|
||||
|
@ -213,3 +220,15 @@ class website_event(http.Controller):
|
|||
if not _values:
|
||||
return request.redirect("/event/%s/" % event_id)
|
||||
return request.redirect("/shop/checkout")
|
||||
|
||||
@website.route(['/event/publish'], type='json', auth="public")
|
||||
def publish(self, id, object):
|
||||
# if a user publish an event, he publish all linked res.partner
|
||||
event = request.registry[object].browse(request.cr, request.uid, int(id))
|
||||
if not event.website_published:
|
||||
if event.organizer_id and not event.organizer_id.website_published:
|
||||
event.organizer_id.write({'website_published': True})
|
||||
if event.address_id and not event.address_id.website_published:
|
||||
event.address_id.write({'website_published': True})
|
||||
|
||||
return controllers.publish(id, object)
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="menu_events" model="website.menu">
|
||||
<field name="name">Events</field>
|
||||
<field name="url">/event</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">40</field>
|
||||
</record>
|
||||
<record id="action_open_website" model="ir.actions.act_url">
|
||||
<field name="name">Website Home</field>
|
||||
<field name="target">self</field>
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="base.res_partner_6" model="res.partner">
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="event.event_0" model="event.event">
|
||||
<field name="website_published">True</field>
|
||||
<field name="twitter_hashtag">openerp</field>
|
||||
|
@ -162,6 +165,9 @@
|
|||
</div>
|
||||
]]></field> </record>
|
||||
|
||||
<record id="base.res_partner_5" model="res.partner">
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="event.event_1" model="event.event">
|
||||
<field name="website_published">True</field>
|
||||
<field name="twitter_hashtag">openerp</field>
|
||||
|
@ -323,11 +329,20 @@
|
|||
]]></field>
|
||||
</record>
|
||||
|
||||
<record id="base.res_partner_14" model="res.partner">
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="event.event_2" model="event.event">
|
||||
<field name="website_published">True</field>
|
||||
<field name="twitter_hashtag">openerp</field>
|
||||
</record>
|
||||
|
||||
<record id="base.res_partner_2" model="res.partner">
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="base.res_partner_address_4" model="res.partner">
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="event.event_3" model="event.event">
|
||||
<field name="website_published">True</field>
|
||||
<field name="twitter_hashtag">openerp</field>
|
||||
|
|
|
@ -51,6 +51,21 @@ class event(osv.osv):
|
|||
'website_published': False,
|
||||
}
|
||||
|
||||
def _check_organizer_id_published(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.website_published and obj.organizer_id and not obj.organizer_id.website_published:
|
||||
return False
|
||||
return True
|
||||
def _check_address_id_published(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.website_published and obj.address_id and not obj.address_id.website_published:
|
||||
return False
|
||||
return True
|
||||
_constraints = [
|
||||
(_check_organizer_id_published, "This event can't be published if the field Orginizer is not website published.", ['organizer_id','website_published']),
|
||||
(_check_address_id_published, "This event can't be published if the field Location is not website published.", ['address_id','website_published']),
|
||||
]
|
||||
|
||||
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
|
||||
partner = self.browse(cr, uid, ids[0], context=context)
|
||||
if partner.address_id:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_event_event_public,event.event.public,event.model_event_event,base.group_public,1,0,0,0
|
||||
access_event_type_public,event.type.public,event.model_event_type,base.group_public,1,0,0,0
|
||||
access_event_event_ticket_public,event.event.ticket.public,event_sale.model_event_event_ticket,base.group_public,1,0,0,0
|
||||
access_event_product_product_public,event.product.product.public,product.model_product_product,base.group_public,1,0,0,0
|
||||
access_event_product_product_public,event.product.product.public,product.model_product_product,base.group_public,1,0,0,0
|
||||
|
|
|
|
@ -3,34 +3,30 @@
|
|||
<data>
|
||||
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="header_footer_custom" inherit_id="website.layout">
|
||||
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
|
||||
<li><a t-href="/event">Events</a></li>
|
||||
</xpath>
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<template id="header_footer_custom" inherit_id="website.layout" name="Footer Events Link">
|
||||
<xpath expr="//footer//ul[@name='products']" position="inside">
|
||||
<li><a t-href="/event">Events</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Page -->
|
||||
<template id="index" name="Events" page="True">
|
||||
<!-- Page -->
|
||||
<template id="index" name="Events">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="oe_structure"/>
|
||||
<div class="container">
|
||||
<div class="oe_structure"/>
|
||||
<h1 class="text-center">
|
||||
Our Events
|
||||
Next Events
|
||||
</h1>
|
||||
<h3 class="text-center text-muted">
|
||||
<t t-esc="current_date"/><t t-if="current_type">,
|
||||
<t t-esc="current_type.name"/></t><t t-if="current_country">,
|
||||
<t t-esc="current_date or ''"/><span t-if="current_type"><t t-if="current_date">,</t>
|
||||
<t t-esc="current_type.name"/></span><span t-if="current_country"><t t-if="current_type or current_date">,</t>
|
||||
<t t-esc="current_country.name"/>
|
||||
</t>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="row mt16 mb32">
|
||||
<div class="col-md-3 col-sm-4 css_noprint" id="left_column">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="nav-header">Date</li>
|
||||
<t t-foreach="dates" t-as="date">
|
||||
<li t-att-class="searches.get('date') == date[0] and 'active' or ''" t-if="date[3]">
|
||||
<a t-href="/event/#{ search_path }&date=#{ date[0] }"><t t-esc="date[1]"/>
|
||||
|
@ -40,7 +36,7 @@
|
|||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<div class="col-sm-7 col-md-6" id="middle_column">
|
||||
<t t-call="website.pager" >
|
||||
<t t-set="classname">pull-left</t>
|
||||
</t>
|
||||
|
@ -48,14 +44,11 @@
|
|||
</div>
|
||||
<ul class="media-list">
|
||||
<li t-foreach="event_ids" t-as="event" class="media" data-publish="">
|
||||
<t t-call="website.publish_management"><t t-set="object" t-value="event"/></t>
|
||||
<div class="media-body">
|
||||
<span t-if="not event.event_ticket_ids" class="label label-danger pull-right">Registration Closed</span>
|
||||
<t t-if="event.event_ticket_ids">
|
||||
<span t-if="event.register_avail == 9999" class="label label-default pull-right label-info">Tickets Available</span>
|
||||
<span t-if="not event.register_avail" class="label label-danger pull-right">Sold Out</span>
|
||||
<span t-if="event.register_avail and event.register_avail != 9999" t-attf-class="label label-default pull-right label-#{ event.register_avail <= 10 and 'warning' or 'info' }">
|
||||
Tickets Available
|
||||
<span t-if="event.register_avail and event.register_avail <= ((event.register_max or 0) / 4)" class="label pull-right label-info">
|
||||
Only <t t-esc="event.register_avail"/> Remaining
|
||||
</span>
|
||||
</t>
|
||||
<h4 class="media-heading"><a t-href="/event/#{ event.id }/"><span t-field="event.name"> </span></a></h4>
|
||||
|
@ -76,6 +69,10 @@
|
|||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-2 col-md-3 oe_structure" id="right_column">
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-md-8 col-lg-offset-4 text-center">
|
||||
<t t-call="website.pager" />
|
||||
|
@ -87,10 +84,45 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="event_category" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Category">
|
||||
<template id="event_right_photos" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Photos">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
<div class="oe_demo">
|
||||
<img src="http://ebmedia.eventbrite.com/s3-build/17022-rc2013-11-04-acfb89e/django/images/pages/home/featcats_bizpro.jpg"/>
|
||||
<div class="text-center"><a href="/">Photos of Events</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mb16">
|
||||
<div class="oe_demo">
|
||||
<img src="http://ebmedia.eventbrite.com/s3-build/17022-rc2013-11-04-acfb89e/django/images/pages/home/featcats_bizpro.jpg"/>
|
||||
<div class="text-center"><a href="/">Customer Quotes</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_right_quotes" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Quotes">
|
||||
<xpath expr="//div[@id='right_column']" position="inside">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb16">
|
||||
<blockquote class="oe_snippet_body">
|
||||
<p>
|
||||
Write here a quote from one of your attendees.
|
||||
It gives confidence in your
|
||||
events.
|
||||
</p>
|
||||
<small>Author</small>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="event_category" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Filter by Category">
|
||||
<xpath expr="//div[@id='left_column']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<li class="nav-header">Category</li>
|
||||
<t t-foreach="types">
|
||||
<li t-if="type" t-att-class="searches.get('type') == str(type and type[0]) and 'active' or ''">
|
||||
<a t-href="/event/#{ search_path }&type=#{ type[0] }"><t t-esc="type[1]"/>
|
||||
|
@ -101,10 +133,9 @@
|
|||
</ul>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="event_location" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Location">
|
||||
<template id="event_location" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Filter by Country">
|
||||
<xpath expr="//div[@id='left_column']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt32">
|
||||
<li class="nav-header">Location</li>
|
||||
<t t-foreach="countries">
|
||||
<li t-if="country_id" t-att-class="searches.get('country') == str(country_id and country_id[0]) and 'active' or ''">
|
||||
<a t-href="/event/#{ search_path }&country=#{ country_id[0] }"><t t-esc="country_id[1]"/>
|
||||
|
@ -124,7 +155,7 @@
|
|||
|
||||
<h1 class="text-center" t-field="event_id.name"></h1>
|
||||
<h4 class="text-center text-muted">
|
||||
<i class="icon-time"></i> <span t-field="event_id.date_begin"/> to
|
||||
<i class="icon-time"></i> <span t-field="event_id.date_begin"/> to
|
||||
<span t-field="event_id.date_end"/>
|
||||
</h4>
|
||||
<h4 t-if="event_id.city and event_id.country_id" class="text-center text-muted">
|
||||
|
@ -177,7 +208,11 @@
|
|||
<ul class="media-list" id="comment">
|
||||
<li t-foreach="event_id.website_message_ids" t-as="comment" class="media">
|
||||
<div class="media-body">
|
||||
<t t-call="website.publish_management"><t t-set="object" t-value="comment"/></t>
|
||||
<t t-call="website.publish_management">
|
||||
<t t-set="object" t-value="comment"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<t t-set="publish_controller">/event/publish</t>
|
||||
</t>
|
||||
<t t-raw="comment.body"/>
|
||||
<small class="pull-right muted text-right">
|
||||
<div t-field="comment.author_id"/>
|
||||
|
@ -192,7 +227,12 @@
|
|||
|
||||
<div class="panel panel-default" t-if="event_id.address_id">
|
||||
<div class="panel-heading">
|
||||
<t t-call="website.publish_management"><t t-set="object" t-value="event_id"/></t>
|
||||
<t t-call="website.publish_management">
|
||||
<t t-set="object" t-value="event_id"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<t t-set="publish_controller">/event/publish</t>
|
||||
<t t-set="style">position: relative; top: -80px;</t>
|
||||
</t>
|
||||
<h4>Where</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
@ -262,12 +302,12 @@
|
|||
|
||||
<ul class="list-unstyled">
|
||||
<li t-if="event_id.type">
|
||||
<a t-att-href="'/event?type='+str(event_id.type.id)"><strong><span class="icon-double-angle-right"/> Other <t t-esc="event_id.type.name"/></strong></a>
|
||||
<a t-href="/event?type=#{event_id.type.id}"><strong><span class="icon-double-angle-right"/> Other <t t-esc="event_id.type.name"/></strong></a>
|
||||
</li>
|
||||
<li t-if="event_id.country_id">
|
||||
<a t-att-href="'/event?country='+str(event_id.country_id.id)"><strong><span class="icon-double-angle-right"/> Other Events in <span t-esc="event_id.country_id.name"/></strong></a>
|
||||
<a t-href="/event?country=#{event_id.country_id.id}"><strong><span class="icon-double-angle-right"/> Other Events in <span t-esc="event_id.country_id.name"/></strong></a>
|
||||
</li>
|
||||
<li><a href="/event"><strong><span class="icon-double-angle-right"/> All Events</strong></a></li>
|
||||
<li><a t-href="/event"><strong><span class="icon-double-angle-right"/> All Events</strong></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="view_event_form">
|
||||
<field name="name">event.event.website.form</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event_sale.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="organizer_id" position="after">
|
||||
<field name="website_published"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -11,4 +11,4 @@ class hr(osv.osv):
|
|||
}
|
||||
|
||||
def img(self, cr, uid, ids, field='image_small', context=None):
|
||||
return "/web/binary/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])
|
||||
return "/website/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.addons.website.models import website
|
||||
from openerp.addons.website.controllers.main import Website as controllers
|
||||
controllers = controllers()
|
||||
|
||||
import base64
|
||||
|
||||
|
||||
class website_hr_recruitment(http.Controller):
|
||||
|
||||
@website.route(['/jobs', '/jobs/page/<int:page>/', '/department/<id>/', '/department/<id>/page/<int:page>/'], type='http', auth="public")
|
||||
def jobs(self, id=0, page=1, **post):
|
||||
@website.route(['/jobs', '/jobs/page/<int:page>/', '/department/<id>/', '/department/<id>/page/<int:page>/'], type='http', auth="public", multilang=True)
|
||||
def jobs(self, id=0, page=1):
|
||||
id = id and int(id) or 0
|
||||
hr_job_obj = request.registry['hr.job']
|
||||
hr_department_obj = request.registry['hr.department']
|
||||
|
@ -36,7 +39,7 @@ class website_hr_recruitment(http.Controller):
|
|||
step = 10
|
||||
pager = request.website.pager(url="/jobs/", total=len(jobpost_ids), page=page, step=step, scope=5)
|
||||
jobpost_ids = hr_job_obj.search(request.cr, request.uid, domain, limit=step, offset=pager['offset'])
|
||||
|
||||
|
||||
values = {
|
||||
'active': active,
|
||||
'companies': companies,
|
||||
|
@ -47,16 +50,15 @@ class website_hr_recruitment(http.Controller):
|
|||
}
|
||||
return request.website.render("website_hr_recruitment.index", values)
|
||||
|
||||
@website.route(['/job/detail/<id>'], type='http', auth="public")
|
||||
def detail(self, id=0):
|
||||
id = id and int(id) or 0
|
||||
@website.route(['/job/detail/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
|
||||
def detail(self, job):
|
||||
values = {
|
||||
'job': request.registry['hr.job'].browse(request.cr, request.uid, id),
|
||||
'vals_date': request.registry['hr.job'].browse(request.cr, request.uid, id).write_date.split(' ')[0]
|
||||
'job': job,
|
||||
'vals_date': job.write_date.split(' ')[0],
|
||||
}
|
||||
return request.website.render("website_hr_recruitment.detail", values)
|
||||
|
||||
@website.route(['/job/success'], type='http', auth="admin")
|
||||
@website.route(['/job/success'], type='http', auth="admin", multilang=True)
|
||||
def success(self, **post):
|
||||
id = request.registry['hr.applicant'].create(request.cr, request.uid, post)
|
||||
if post['ufile']:
|
||||
|
@ -69,28 +71,23 @@ class website_hr_recruitment(http.Controller):
|
|||
'res_id': id
|
||||
}
|
||||
request.registry['ir.attachment'].create(request.cr, request.uid, attachment_values)
|
||||
website = request.registry['website']
|
||||
values = {
|
||||
'jobid': post['job_id']
|
||||
}
|
||||
return request.website.render("website_hr_recruitment.thankyou", values)
|
||||
|
||||
@website.route(['/apply/<int:id>'], type='http', auth="public")
|
||||
def applyjobpost(self, id=0):
|
||||
id = id and int(id) or 0
|
||||
job = request.registry['hr.job'].browse(request.cr, request.uid, id)
|
||||
values = {
|
||||
'job': job
|
||||
}
|
||||
return request.website.render("website_hr_recruitment.applyjobpost", values)
|
||||
|
||||
@website.route('/recruitment/published', type='json', auth="admin")
|
||||
def published (self, id, **post):
|
||||
hr_job = request.registry['hr.job']
|
||||
@website.route(['/apply/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
|
||||
def applyjobpost(self, job):
|
||||
return request.website.render("website_hr_recruitment.applyjobpost", { 'job': job })
|
||||
|
||||
@website.route('/job/publish', type='json', auth="admin", multilang=True)
|
||||
def publish (self, id, object):
|
||||
res = controllers.publish(id, object)
|
||||
|
||||
hr_job = request.registry[object]
|
||||
id = int(id)
|
||||
rec = hr_job.browse(request.cr, request.uid, id)
|
||||
vals = {}
|
||||
|
||||
if rec.website_published:
|
||||
vals['state'] = 'recruit'
|
||||
if not rec.no_of_recruitment:
|
||||
|
@ -99,6 +96,5 @@ class website_hr_recruitment(http.Controller):
|
|||
vals['state'] = 'open'
|
||||
hr_job.write(request.cr, request.uid, [rec.id], vals, context=request.context)
|
||||
|
||||
obj = hr_job.browse(request.cr, request.uid, id, context=request.context)
|
||||
return { 'count': obj.no_of_recruitment, 'state': obj.state, 'published': obj.website_published }
|
||||
return res
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="menu_jobs" model="website.menu">
|
||||
<field name="name">Jobs</field>
|
||||
<field name="url">/jobs</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">60</field>
|
||||
</record>
|
||||
<record id="action_open_website" model="ir.actions.act_url">
|
||||
<field name="name">Website Recruitment Form</field>
|
||||
<field name="target">self</field>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
$(function () {
|
||||
$(this).ajaxComplete(function(event, xhr, settings) {
|
||||
var data = JSON.parse(settings.data).params;
|
||||
if (settings.url == "/website/publish") {
|
||||
var $data = $(".oe_website_hr_recruitment .js_publish_management[data-id='" + data.id + "'][data-object='" + data.object + "']");
|
||||
if ($data.length) {
|
||||
settings.jsonpCallback = openerp.jsonRpc('/recruitment/published', 'call', data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -7,16 +7,13 @@
|
|||
<field name="description">Job Posts on your website</field>
|
||||
</record>
|
||||
|
||||
<template id="job_footer_custom" inherit_id="website.layout" name="Custom Footer Job">
|
||||
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
|
||||
<li><a href="/jobs">Jobs</a></li>
|
||||
</xpath>
|
||||
<template id="job_footer_custom" inherit_id="website.layout" name="Footer Job Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a href="/jobs">Jobs</a></li>
|
||||
<li><a t-href="/jobs">Jobs</a></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="index" name="Jobs" page="True">
|
||||
<template id="index" name="Jobs">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="oe_structure"/>
|
||||
|
@ -36,7 +33,7 @@
|
|||
<t t-if="job.no_of_recruitment">
|
||||
<span class="label label-default pull-right label-info"><t t-esc="vals[job.id]['count']"/> Vacancies.</span>
|
||||
</t>
|
||||
<h4 class="media-heading"><a t-attf-href="/job/detail/#{ job.id }/"><span t-field="job.name"> </span></a></h4>
|
||||
<h4 class="media-heading"><a t-href="/job/detail/#{ job.id }/"><span t-field="job.name"> </span></a></h4>
|
||||
<div t-if="companies[0].country_id">
|
||||
<i class="icon-map-marker"/> <span t-field="companies[0].city"> </span> <span t-if="companies[0].state_id" t-field="companies[0].state_id.name"> </span>, <span t-field="companies[0].country_id.name"> </span>
|
||||
</div>
|
||||
|
@ -56,10 +53,6 @@
|
|||
|
||||
<template id="detail">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/recruitment.js"></script>
|
||||
<t t-raw="head or ''"/>
|
||||
</t>
|
||||
<t t-set="additional_title">Job Detail</t>
|
||||
<div id="wrap">
|
||||
<div class="container oe_website_hr_recruitment">
|
||||
|
@ -73,6 +66,7 @@
|
|||
<t t-call="website.publish_management">
|
||||
<t t-set="object" t-value="job"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<t t-set="publish_controller">/job/publish</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,7 +77,7 @@
|
|||
<h5 class="text-center text-muted">
|
||||
<i class="icon-time"/> <span><t t-esc="vals_date"/></span>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container oe_structure">
|
||||
<div class="row">
|
||||
<div t-if="job.website_description">
|
||||
|
@ -92,11 +86,11 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center mt16 mb16">
|
||||
<a t-attf-href="/apply/#{ job.id }/" class="btn btn-primary btn-lg">Apply</a>
|
||||
<a t-href="/apply/#{ job.id }/" class="btn btn-primary btn-lg">Apply</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -112,6 +106,7 @@
|
|||
<h1 class="text-center">Apply for <span t-field="job.name"/></h1>
|
||||
<div class="row">
|
||||
<section id="forms">
|
||||
<!-- TODO Multilingual form action support ? -->
|
||||
<form class="form-horizontal mt32" action="/job/success" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" t-att-value="job.department_id.id" name="department_id"/>
|
||||
<input type="hidden" t-att-value="job.id" name="job_id"/>
|
||||
|
@ -170,10 +165,10 @@
|
|||
<xpath expr="//div[@id='jobs_grid']" position="before">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li t-att-class=" '' if active else 'active' "><a href="/jobs">All Departments</a></li>
|
||||
<li t-att-class=" '' if active else 'active' "><a t-href="/jobs">All Departments</a></li>
|
||||
<t t-foreach="departments" t-as="department">
|
||||
<li t-att-class="department.id == active and 'active' or ''">
|
||||
<a t-attf-href="/department/#{ department.id }/" ><span t-field="department.name"/></a>
|
||||
<a t-href="/department/#{ department.id }/" ><span t-field="department.name"/></a>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
|
|
|
@ -3,17 +3,20 @@
|
|||
<data>
|
||||
|
||||
<template id="follow">
|
||||
<div name="follow">
|
||||
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
|
||||
class="pull-right js_follow" t-if="editable" t-ignore="true">
|
||||
<div name="follow" t-if="editable">
|
||||
<a href="#" t-att-data-id="object.id"
|
||||
t-att-data-object="object._name"
|
||||
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
|
||||
class="pull-right js_follow" t-ignore="true">
|
||||
<span t-attf-class="label label-success css_follow">Follow</span>
|
||||
<span t-attf-class="label label-danger css_unfollow">Unfollow</span>
|
||||
<span t-attf-class="label label-success css_followed">Following</span>
|
||||
<span t-attf-class="label label-danger css_unfollowed">Not Following</span>
|
||||
</a>
|
||||
<input type="email" name="email" class="js_follow_email css_unfollowed_email"
|
||||
t-att-value="email"
|
||||
t-att-placeholder="email or 'Email Address'"/>
|
||||
<input type="email" name="email"
|
||||
class="js_follow_email css_unfollowed_email"
|
||||
t-att-value="email"
|
||||
t-att-placeholder="email or 'Email Address'"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class WebsiteMembership(http.Controller):
|
|||
def partners_ref(self, partner_id=0, **post):
|
||||
partner_obj = request.registry['res.partner']
|
||||
if request.context['is_public_user']:
|
||||
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_pushished', '=', True), ('id', '=', partner_id)], context=request.context)
|
||||
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_published', '=', True), ('id', '=', partner_id)], context=request.context)
|
||||
else:
|
||||
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
|
||||
if not partner_ids:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<data>
|
||||
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Associations Link">
|
||||
<xpath expr="//footer//div[@name='info']/ul" position="inside">
|
||||
<li><a t-href="/members/">Associations</a></li>
|
||||
</xpath>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<!-- Option: index: Left Google Map -->
|
||||
<template id="opt_index_google_map" name="Left World Map"
|
||||
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
|
||||
<xpath expr="//div[@id='left_column']/ul[last()]" position="after">
|
||||
<xpath expr="//div[@id='left_column']/ul[1]" position="before">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li class="nav-header"><h3>World Map</h3></li>
|
||||
<ul class="nav">
|
||||
|
|
|
@ -28,13 +28,13 @@ from openerp.osv import osv
|
|||
class Website(osv.Model):
|
||||
_inherit = "website"
|
||||
|
||||
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
|
||||
def preprocess_request(self, cr, uid, ids, request, context=None):
|
||||
project_obj = request.registry['project.project']
|
||||
project_ids = project_obj.search(cr, uid, [('privacy_visibility', "=", "public")], context=request.context)
|
||||
|
||||
request.context['website_project_ids'] = project_obj.browse(cr, uid, project_ids, request.context)
|
||||
|
||||
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
|
||||
return super(Website, self).preprocess_request(cr, uid, ids, request, context)
|
||||
|
||||
|
||||
class website_project(http.Controller):
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
<data>
|
||||
|
||||
<!-- Layout add nav and footer -->
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
|
||||
<template id="footer_custom" inherit_id="website.layout" name="Footer Project's Links">
|
||||
<xpath expr="//footer//ul[@name='products']" position="inside">
|
||||
<li t-foreach="website_project_ids" t-as="project"><a t-href="/project/#{ project.id }/"><t t-esc="project.name"/></a></li>
|
||||
<li t-foreach="website_project_ids" t-as="project"><a t-href="/project/#{ project.id }/" t-field="project.name"/></li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
import uuid
|
||||
import simplejson
|
||||
|
||||
import werkzeug.exceptions
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
from openerp.addons.website.models import website
|
||||
import random
|
||||
import uuid
|
||||
import urllib
|
||||
import simplejson
|
||||
|
||||
def get_order(order_id=None):
|
||||
order_obj = request.registry.get('sale.order')
|
||||
|
@ -52,11 +53,11 @@ def get_current_order():
|
|||
|
||||
class Website(osv.osv):
|
||||
_inherit = "website"
|
||||
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
|
||||
def preprocess_request(self, cr, uid, ids, request, context=None):
|
||||
request.context.update({
|
||||
'website_sale_order': get_current_order(),
|
||||
})
|
||||
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
|
||||
return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)
|
||||
|
||||
class Ecommerce(http.Controller):
|
||||
|
||||
|
@ -212,7 +213,6 @@ class Ecommerce(http.Controller):
|
|||
col += 1
|
||||
line += 1
|
||||
|
||||
print bin_packing_list
|
||||
return bin_packing_list
|
||||
|
||||
def get_products(self, product_ids):
|
||||
|
@ -222,20 +222,24 @@ class Ecommerce(http.Controller):
|
|||
product_ids = [id for id in product_ids if id in product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)]
|
||||
return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
|
||||
|
||||
def has_search_attributes(self, attribute_id, value_id=None):
|
||||
if request.httprequest.args.get('attributes'):
|
||||
attributes = simplejson.loads(request.httprequest.args['attributes'])
|
||||
def has_search_filter(self, attribute_id, value_id=None):
|
||||
if request.httprequest.args.get('filter'):
|
||||
filter = simplejson.loads(request.httprequest.args['filter'])
|
||||
else:
|
||||
attributes = []
|
||||
for key_val in attributes:
|
||||
filter = []
|
||||
for key_val in filter:
|
||||
if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
|
||||
return key_val
|
||||
return False
|
||||
|
||||
@website.route(['/shop/attributes/'], type='http', auth="public", multilang=True)
|
||||
def attributes(self, **post):
|
||||
attributes = []
|
||||
@website.route(['/shop/filter/'], type='http', auth="public", multilang=True)
|
||||
def filter(self, add_filter="", **post):
|
||||
index = []
|
||||
filter = []
|
||||
if add_filter:
|
||||
filter = simplejson.loads(add_filter)
|
||||
for filt in filter:
|
||||
index.append(filt[0])
|
||||
for key, val in post.items():
|
||||
cat = key.split("-")
|
||||
if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
|
||||
|
@ -247,16 +251,23 @@ class Ecommerce(http.Controller):
|
|||
_max = int(post.pop("att-%s-max" % cat[1]))
|
||||
_min = int(val)
|
||||
if (minmem != _min or maxmem != _max) and cat_id not in index:
|
||||
attributes.append([cat_id , [_min, _max] ])
|
||||
filter.append([cat_id , [_min, _max] ])
|
||||
index.append(cat_id)
|
||||
elif cat_id not in index:
|
||||
attributes.append([ cat_id, int(cat[2]) ])
|
||||
filter.append([ cat_id, int(cat[2]) ])
|
||||
index.append(cat_id)
|
||||
else:
|
||||
attributes[index.index(cat_id)].append( int(cat[2]) )
|
||||
cat[2] = int(cat[2])
|
||||
if cat[2] not in filter[index.index(cat_id)][1:]:
|
||||
filter[index.index(cat_id)].append( cat[2] )
|
||||
post.pop(key)
|
||||
|
||||
return request.redirect("/shop/?attributes=%s&%s" % (simplejson.dumps(attributes).replace(" ", ""), urllib.urlencode(post)))
|
||||
return request.redirect("/shop/?filter=%s%s%s%s" % (
|
||||
simplejson.dumps(filter),
|
||||
add_filter and "&add_filter=%s" % add_filter or "",
|
||||
post.get("search") and "&search=%s" % post.get("search") or "",
|
||||
post.get("category") and "&category=%s" % post.get("category") or ""
|
||||
))
|
||||
|
||||
def attributes_to_ids(self, attributes):
|
||||
obj = request.registry.get('product.attribute.product')
|
||||
|
@ -272,7 +283,7 @@ class Ecommerce(http.Controller):
|
|||
return [r["product_id"][0] for r in att]
|
||||
|
||||
@website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
|
||||
def category(self, category=0, attributes="", page=0, **post):
|
||||
def category(self, category=0, filter="", page=0, **post):
|
||||
# TDE-NOTE: shouldn't we do somethign about product_template without variants ???
|
||||
# TDE-NOTE: is there a reason to call a method category when the route is
|
||||
# basically a shop without category_id speceified ?
|
||||
|
@ -294,10 +305,10 @@ class Ecommerce(http.Controller):
|
|||
cat_id = int(category)
|
||||
domain = [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
|
||||
|
||||
if attributes:
|
||||
attributes = simplejson.loads(attributes)
|
||||
if attributes:
|
||||
ids = self.attributes_to_ids(attributes)
|
||||
if filter:
|
||||
filter = simplejson.loads(filter)
|
||||
if filter:
|
||||
ids = self.attributes_to_ids(filter)
|
||||
domain = [('id', 'in', ids or [0] )] + domain
|
||||
|
||||
step = 20
|
||||
|
@ -319,32 +330,33 @@ class Ecommerce(http.Controller):
|
|||
'Ecommerce': self,
|
||||
'product_ids': product_ids,
|
||||
'product_ids_for_holes': fill_hole,
|
||||
'search': post or dict(),
|
||||
'search': {
|
||||
'search': post.get('search') or '',
|
||||
'category': category,
|
||||
'filter': filter or '',
|
||||
},
|
||||
'pager': pager,
|
||||
'styles': styles,
|
||||
'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
|
||||
}
|
||||
return request.website.render("website_sale.products", values)
|
||||
|
||||
@website.route(['/shop/product/<int:product_id>/'], type='http', auth="public", multilang=True)
|
||||
def product(self, product_id=0, **post):
|
||||
@website.route(['/shop/product/<model("product.template"):product>/'], type='http', auth="public", multilang=True)
|
||||
def product(self, product, search='', category='', filter='', promo=None, lang_code=None):
|
||||
|
||||
if 'promo' in post:
|
||||
self.change_pricelist(post.get('promo'))
|
||||
if promo:
|
||||
self.change_pricelist(promo)
|
||||
|
||||
product_obj = request.registry.get('product.template')
|
||||
category_obj = request.registry.get('product.public.category')
|
||||
|
||||
category_ids = category_obj.search(request.cr, request.uid, [(1, '=', 1)], context=request.context)
|
||||
category_ids = category_obj.search(request.cr, request.uid, [], context=request.context)
|
||||
category_list = category_obj.name_get(request.cr, request.uid, category_ids, context=request.context)
|
||||
category_list = sorted(category_list, key=lambda category: category[1])
|
||||
|
||||
category = None
|
||||
if post.get('category_id') and int(post.get('category_id')):
|
||||
category = category_obj.browse(request.cr, request.uid, int(post.get('category_id')), context=request.context)
|
||||
if category:
|
||||
category = category_obj.browse(request.cr, request.uid, int(category), context=request.context)
|
||||
|
||||
request.context['pricelist'] = self.get_pricelist()
|
||||
product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
|
||||
|
||||
values = {
|
||||
'Ecommerce': self,
|
||||
|
@ -352,15 +364,26 @@ class Ecommerce(http.Controller):
|
|||
'category_list': category_list,
|
||||
'main_object': product,
|
||||
'product': product,
|
||||
'search': post or dict(),
|
||||
'search': {
|
||||
'search': search,
|
||||
'category': category and str(category.id),
|
||||
'filter': filter,
|
||||
}
|
||||
}
|
||||
return request.website.render("website_sale.product", values)
|
||||
|
||||
@website.route(['/shop/add_product/', '/shop/category/<int:cat_id>/add_product/'], type='http', auth="public", multilang=True)
|
||||
def add_product(self, cat_id=0, **post):
|
||||
product_id = request.registry.get('product.product').create(request.cr, request.uid,
|
||||
{'name': 'New Product', 'public_categ_id': cat_id}, request.context)
|
||||
return request.redirect("/shop/product/%s/?enable_editor=1" % product_id)
|
||||
if request.httprequest.method != 'POST':
|
||||
return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
|
||||
|
||||
Product = request.registry.get('product.product')
|
||||
product_id = Product.create(request.cr, request.uid, {
|
||||
'name': 'New Product', 'public_categ_id': cat_id
|
||||
}, context=request.context)
|
||||
product = Product.browse(request.cr, request.uid, product_id, context=request.context)
|
||||
|
||||
return request.redirect("/shop/product/%s/?enable_editor=1" % product.product_tmpl_id.id)
|
||||
|
||||
def get_pricelist(self):
|
||||
if not request.httprequest.session.get('ecommerce_pricelist'):
|
||||
|
@ -443,8 +466,7 @@ class Ecommerce(http.Controller):
|
|||
else:
|
||||
order_line_id = order_line_obj.create(request.cr, SUPERUSER_ID, values, context=request.context)
|
||||
order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
|
||||
|
||||
return [quantity, order.get_total_quantity()]
|
||||
return quantity
|
||||
|
||||
@website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
|
||||
def mycart(self, **post):
|
||||
|
@ -458,7 +480,7 @@ class Ecommerce(http.Controller):
|
|||
product_ids = []
|
||||
if order:
|
||||
for line in order.order_line:
|
||||
suggested_ids += [p.id for p in line.product_id and line.product_id.suggested_product_ids or [] for line in order.order_line]
|
||||
suggested_ids += [p.id for p in line.product_id and line.product_id.suggested_product_ids or []]
|
||||
product_ids.append(line.product_id.id)
|
||||
suggested_ids = list(set(suggested_ids) - set(product_ids))
|
||||
if suggested_ids:
|
||||
|
@ -484,7 +506,11 @@ class Ecommerce(http.Controller):
|
|||
|
||||
@website.route(['/shop/add_cart_json/'], type='json', auth="public")
|
||||
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
|
||||
return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
|
||||
quantity = self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
|
||||
order = get_current_order()
|
||||
return [quantity, order.get_total_quantity(), order.amount_total, request.website.render("website_sale.total", {
|
||||
'website_sale_order': order
|
||||
}).strip()]
|
||||
|
||||
@website.route(['/shop/set_cart_json/'], type='json', auth="public")
|
||||
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):
|
||||
|
|
|
@ -38,7 +38,7 @@ class attributes(osv.Model):
|
|||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, translate=True, required=True),
|
||||
'type': fields.selection([('distinct', 'Distinct'), ('float', 'Float')], "Type", required=True),
|
||||
'type': fields.selection([('distinct', 'Textual Value'), ('float', 'Numeric Value')], "Type", required=True),
|
||||
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values'),
|
||||
'product_ids': fields.one2many('product.attribute.product', 'attribute_id', 'Products'),
|
||||
|
||||
|
@ -48,9 +48,11 @@ class attributes(osv.Model):
|
|||
'float_min': fields.function(_get_float_min, type='float', string="Min", store={
|
||||
'product.attribute.product': (_get_min_max, ['value','attribute_id'], 20),
|
||||
}),
|
||||
'visible': fields.boolean('Display Filter on Website'),
|
||||
}
|
||||
_defaults = {
|
||||
'type': 'distinct'
|
||||
'type': 'distinct',
|
||||
'visible': True,
|
||||
}
|
||||
|
||||
class attributes_value(osv.Model):
|
||||
|
@ -63,9 +65,10 @@ class attributes_value(osv.Model):
|
|||
|
||||
class attributes_product(osv.Model):
|
||||
_name = "product.attribute.product"
|
||||
_order = 'attribute_id, value_id, value'
|
||||
_columns = {
|
||||
'value': fields.float('Value'),
|
||||
'value_id': fields.many2one('product.attribute.value', 'Distinct Value'),
|
||||
'value': fields.float('Numeric Value'),
|
||||
'value_id': fields.many2one('product.attribute.value', 'Textual Value'),
|
||||
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),
|
||||
'product_id': fields.many2one('product.template', 'Product', required=True),
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@ access_product_template_public,product.template.public,product.model_product_tem
|
|||
access_product_category_,product.category.public,product.model_product_category,base.group_public,1,0,0,0
|
||||
access_product_category_public,product.category.public,product.model_product_public_category,base.group_public,1,0,0,0
|
||||
access_product_pricelist_version_public,product.pricelist.version.public,product.model_product_pricelist_version,base.group_public,1,0,0,0
|
||||
access_product_pricelist_public,product.pricelist.public,product.model_product_pricelist,base.group_public,1,0,0,0
|
||||
access_product_product_price_type_public,product.price.type.public,product.model_product_price_type,base.group_public,1,0,0,0
|
||||
access_sale_order_public,sale.order.public,model_sale_order,base.group_public,1,0,0,0
|
||||
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
|
||||
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
|
||||
access_product_attribute,product.attribute.public,website_sale.model_product_attribute,base.group_public,1,0,0,0
|
||||
access_product_attribute_value,product.attribute.value.public,website_sale.model_product_attribute_value,base.group_public,1,0,0,0
|
||||
access_product_attribute_product,product.attribute.product.public,website_sale.model_product_attribute_product,base.group_public,1,0,0,0
|
|
|
@ -42,5 +42,16 @@
|
|||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="product_pricelist_public" model="ir.rule">
|
||||
<field name="name">Public product pricelist</field>
|
||||
<field name="model_id" ref="product.model_product_pricelist"/>
|
||||
<field name="domain_force">[('id','=',session.get('ecommerce_pricelist'))]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
|
@ -133,7 +133,7 @@
|
|||
height: 560px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (max-width: 768px) {
|
||||
#products_grid table, #products_grid tbody, #products_grid tr, #products_grid td {
|
||||
float: left;
|
||||
width: 100%;
|
||||
|
@ -145,6 +145,16 @@
|
|||
height: 300px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.products_pager {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.products_pager .pagination {
|
||||
float: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
#products_grid .oe_product {
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
.oe-height-8
|
||||
height: 560px
|
||||
|
||||
@media (max-width: 600px)
|
||||
@media (max-width: 768px)
|
||||
#products_grid
|
||||
table, tbody,tr, td
|
||||
float: left
|
||||
|
@ -132,6 +132,13 @@
|
|||
width: 100%
|
||||
height: 300px
|
||||
display: inline-block
|
||||
.products_pager
|
||||
text-align: center
|
||||
margin: 10px 0
|
||||
.pagination
|
||||
float: none !important
|
||||
margin: 0 !important
|
||||
padding: 0 !important
|
||||
|
||||
@media (max-width: 400px)
|
||||
#products_grid
|
||||
|
|
|
@ -17,7 +17,7 @@ $(document).ready(function () {
|
|||
|
||||
function set_my_cart_quantity(qty) {
|
||||
var $q = $(".my_cart_quantity");
|
||||
$q.parent().parent().toggleClass("hidden", !qty);
|
||||
$q.parent().parent().removeClass("hidden", !qty);
|
||||
$q.html(qty)
|
||||
.hide()
|
||||
.fadeIn(600);
|
||||
|
@ -43,13 +43,21 @@ $(document).ready(function () {
|
|||
var $link = $(ev.currentTarget);
|
||||
var product = $link.attr("href").match(/product_id=([0-9]+)/);
|
||||
var product_id = product ? +product[1] : 0;
|
||||
openerp.jsonRpc("/shop/add_cart_json/", 'call', {'product_id': product_id, 'order_line_id': $link.data('id'), 'remove': $link.is('[href*="/remove_cart/"]')})
|
||||
if (!product) {
|
||||
var line = $link.attr("href").match(/order_line_id=([0-9]+)/);
|
||||
order_line_id = line ? +line[1] : 0;
|
||||
}
|
||||
openerp.jsonRpc("/shop/add_cart_json/", 'call', {
|
||||
'product_id': product_id,
|
||||
'order_line_id': order_line_id,
|
||||
'remove': $link.is('[href*="remove"]')})
|
||||
.then(function (data) {
|
||||
if (!data[0]) {
|
||||
location.reload();
|
||||
}
|
||||
set_my_cart_quantity(data[1]);
|
||||
$link.parents(".input-group:first").find(".js_quantity").val(data[0]);
|
||||
$('[data-oe-model="sale.order"][data-oe-field="amount_total"]').replaceWith(data[3]);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
|
||||
<!-- Layout add nav and footer -->
|
||||
|
||||
<template id="header" inherit_id="website.layout" name="Custom Header">
|
||||
<template id="header" inherit_id="website.layout" name="Header Shop My Cart Link">
|
||||
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
|
||||
<li><a t-href="/shop/">Shop</a></li>
|
||||
<li t-att-class="(not website_sale_order or not website_sale_order.get_total_quantity()) and 'hidden' or ''">
|
||||
<a t-href="/shop/mycart/">
|
||||
<i class="icon-shopping-cart"></i>
|
||||
|
@ -20,7 +19,7 @@
|
|||
<!-- List of categories -->
|
||||
|
||||
<template id="categories_recursive" name="Category list">
|
||||
<li t-att-class="category.id == search.get('category') and 'active' or ''">
|
||||
<li t-att-class="str(category.id) == search.get('category') and 'active' or ''">
|
||||
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/?category=#{ category.id }" t-field="category.name" t-keep-query="search,facettes"></a>
|
||||
<ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
|
||||
<t t-foreach="category.child_id" t-as="category">
|
||||
|
@ -34,6 +33,12 @@
|
|||
|
||||
<!-- Product list -->
|
||||
|
||||
<template id="search" name="Search hidden fields">
|
||||
<input type="hidden" name="category" t-att-value="search.get('category') or ''"/>
|
||||
<input type="hidden" name="filter" t-att-value="search.get('filter') or ''"/>
|
||||
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
|
||||
</template>
|
||||
|
||||
<template id="products_cart" name="Shopping cart">
|
||||
<div class="ribbon-wrapper">
|
||||
<div class="ribbon">Promo</div>
|
||||
|
@ -65,9 +70,11 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template id="products" name="Products" page="True">
|
||||
<template id="products" name="Products">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
|
||||
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
|
||||
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
|
||||
<t t-raw="head or ''"/>
|
||||
|
@ -77,23 +84,27 @@
|
|||
<div class="oe_structure"/>
|
||||
<div class="container oe_website_sale">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 pagination" style="padding-left: 15px;">
|
||||
<a t-if="editable" t-href="/shop/add_product/" class="btn btn-primary btn-default" t-keep-query="category,search,facettes">New Product</a>
|
||||
<div class="col-sm-6 pagination hidden-xs" style="padding-left: 15px;">
|
||||
<form t-if="editable" t-keep-query="category,search,facettes"
|
||||
method="POST" t-action="/shop/add_product">
|
||||
<button class="btn btn-primary">New Product</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-6 products_pager">
|
||||
<t t-call="website.pager">
|
||||
<t t-set="classname">pull-right</t>
|
||||
<t t-set="style">padding-left: 5px;</t>
|
||||
</t>
|
||||
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;" t-keep-query="category,search,facettes">
|
||||
<div class="form-group">
|
||||
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
|
||||
</div>
|
||||
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;">
|
||||
<t t-call="website_sale.search" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='style_default row'>
|
||||
<div class="hidden" id="products_grid_before"></div>
|
||||
<div class="col-md-12" id="products_grid">
|
||||
<t t-if="product_ids">
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<t t-set="table_products" t-value="Ecommerce.get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
|
||||
|
@ -167,9 +178,13 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
<t t-if="not product_ids">
|
||||
<h3 class="text-center text-muted">No product found for this search</h3>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="products_pager">
|
||||
<t t-call="website.pager">
|
||||
<t t-set="classname">pull-right</t>
|
||||
</t>
|
||||
|
@ -231,7 +246,7 @@
|
|||
<div class="col-sm-5">
|
||||
<ol class="breadcrumb">
|
||||
<li><a t-href="/shop">Products</a></li>
|
||||
<li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
|
||||
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
|
||||
<li class="active"><span t-field="product.name"></span></li>
|
||||
</ol>
|
||||
</div><div class="col-sm-3">
|
||||
|
@ -247,17 +262,9 @@
|
|||
</li>
|
||||
</t>
|
||||
</div><div class="col-sm-3 col-sm-offset-1">
|
||||
<form t-action="/shop/" method="get" class="pull-right" t-keep-query="category,facettes">
|
||||
<div class="input-group">
|
||||
<t t-if="search">
|
||||
<t foreach="search.items()" t-as="key">
|
||||
<input t-att-name="key[0]" t-att-value="key[1]"/>
|
||||
</t>
|
||||
</t>
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
|
||||
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
|
||||
</div>
|
||||
</form>
|
||||
<form t-action="/shop/" method="get" class="pull-right">
|
||||
<t t-call="website_sale.search" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -265,7 +272,7 @@
|
|||
<section class="container oe_website_sale mb16" id="product_detail">
|
||||
<div class="row">
|
||||
<div class="col-sm-7 col-md-7 col-lg-7">
|
||||
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image"}'/>
|
||||
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image", "class": "img img-responsive"}'/>
|
||||
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
|
||||
<h1 t-field="product.name">Product Name</h1>
|
||||
|
||||
|
@ -338,9 +345,19 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="product_attributes" inherit_option_id="website_sale.product" name="Product Attributes">
|
||||
<xpath expr="//p[@t-field='product.description_sale']" position="after">
|
||||
<hr t-if="product.website_attribute_ids"/>
|
||||
<p class="text-muted">
|
||||
<t t-set="attr" t-value="None"/>
|
||||
<t t-foreach="product.website_attribute_ids" t-as="attribute"><br t-if="attr and attribute.attribute_id.id != attr"/><t t-if="attribute.attribute_id.id != attr"><span t-field="attribute.attribute_id"/>: </t><t t-if="attribute.attribute_id.id == attr">, </t><t t-if="attribute.attribute_id.type == 'distinct'"><span t-field="attribute.value_id"/></t><t t-if="attribute.attribute_id.type == 'float'"><span t-field="attribute.value"/></t><t t-set="attr" t-value="attribute.attribute_id.id"/></t>
|
||||
</p>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Page Shop my cart -->
|
||||
|
||||
<template id="mycart" name="Your Cart" page="True">
|
||||
<template id="mycart" name="Your Cart">
|
||||
<t t-call="website.layout">
|
||||
<t t-set="head">
|
||||
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
|
||||
|
@ -358,18 +375,6 @@
|
|||
</ul>
|
||||
<h1 class="mb32">Shopping Cart</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-3 text-muted" id="right_column">
|
||||
<h4>Policies</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 30-days money-back guarantee</li>
|
||||
<li>☑ Invoice sent by e-Mail</li>
|
||||
</ul>
|
||||
<h4>Secure Payment</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 256 bit encryption</li>
|
||||
<li>☑ Processed by Ogone</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8 col-md-offset-1 oe_mycart">
|
||||
<div t-if="not website_sale_order or not website_sale_order.order_line" class="well well-lg">
|
||||
Your cart is empty!
|
||||
|
@ -418,14 +423,14 @@
|
|||
<td>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<a t-href="./remove_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 js_add_cart_json">
|
||||
<a t-href="./add_cart/?remove=True&order_line_id=#{ line.id }" class="mb8 js_add_cart_json">
|
||||
<span class="icon-minus"/>
|
||||
</a>
|
||||
</span>
|
||||
<input type="text" class="js_quantity form-control"
|
||||
t-att-data-id="line.id" t-att-value="int(line.product_uom_qty)"/>
|
||||
<span class="input-group-addon">
|
||||
<a t-href="./add_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 float_left js_add_cart_json">
|
||||
<a t-href="./add_cart/?order_line_id=#{ line.id }" class="mb8 float_left js_add_cart_json">
|
||||
<span class="icon-plus"/>
|
||||
</a>
|
||||
</span>
|
||||
|
@ -435,7 +440,7 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class='pull-right mb16' id="mycart_total">
|
||||
<table class='pull-right mb16' id="mycart_total" t-if="website_sale_order">
|
||||
<colgroup>
|
||||
<col width="100"/>
|
||||
<col width="120"/>
|
||||
|
@ -443,11 +448,8 @@
|
|||
<thead>
|
||||
<tr style="border-top: 1px solid #000">
|
||||
<th><h3>Total:</h3></th>
|
||||
<th class="text-right"><h3>
|
||||
<span t-field="website_sale_order.amount_total" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/></h3>
|
||||
<th class="text-right">
|
||||
<h3><t t-call="website_sale.total"/></h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="text-muted">
|
||||
|
@ -466,6 +468,18 @@
|
|||
<a t-if="website_sale_order and website_sale_order.order_line" t-href="/shop/checkout/" class="btn btn-primary pull-right mb32">Process Checkout <span class="icon-long-arrow-right"/></a>
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
<div class="col-md-3 text-muted" id="right_column">
|
||||
<h4>Policies</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 30-days money-back guarantee</li>
|
||||
<li>☑ Invoice sent by e-Mail</li>
|
||||
</ul>
|
||||
<h4>Secure Payment</h4>
|
||||
<ul class="list-unstyled mb32">
|
||||
<li>☑ 256 bit encryption</li>
|
||||
<li>☑ Processed by Ogone</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -477,36 +491,38 @@
|
|||
<!-- Page Shop -->
|
||||
|
||||
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
|
||||
<xpath expr="//div[@id='products_grid']" position="before">
|
||||
<div id="categories" class="col-md-3">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
|
||||
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
|
||||
<t t-foreach="categ[0]" t-as="category">
|
||||
<t t-call="website_sale.categories_recursive"/>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<xpath expr="//div[@id='products_grid_before']" position="inside">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
|
||||
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
|
||||
<t t-foreach="categ[0]" t-as="category">
|
||||
<t t-call="website_sale.categories_recursive"/>
|
||||
</t>
|
||||
</ul>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='products_grid_before']" position="attributes">
|
||||
<attribute name="class">col-md-3</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='products_grid']" position="attributes">
|
||||
<attribute name="class">col-md-9</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="products_attributes" inherit_option_id="website_sale.products_categories" name="Product Attributes">
|
||||
<xpath expr="//div[@id='categories']" position="inside">
|
||||
<form t-action="/shop/attributes/" method="post" t-keep-query="category,search">
|
||||
<template id="products_attributes" inherit_option_id="website_sale.products" name="Product Filters and Attributes">
|
||||
<xpath expr="//div[@id='products_grid_before']" position="inside">
|
||||
<form t-action="/shop/filter/" method="post" t-keep-query="category,search,add_filter">
|
||||
<ul class="nav nav-pills nav-stacked mt16">
|
||||
<t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
|
||||
<t t-foreach="attribute_ids" t-as="attribute_id">
|
||||
<t t-if="attribute_id.visible">
|
||||
<li t-if="attribute_id.value_ids and attribute_id.type == 'distinct'">
|
||||
<div t-field="attribute_id.name"/>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<t t-foreach="attribute_id.value_ids" t-as="value_id">
|
||||
<li t-att-class="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'active' or ''">
|
||||
<li t-att-class="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'active' or ''">
|
||||
<label style="margin: 0 20px;">
|
||||
<input type="checkbox" t-att-name="'att-%s-%s' % (attribute_id.id, value_id.id)"
|
||||
t-att-checked="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'checked' or ''"/>
|
||||
t-att-checked="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'checked' or ''"/>
|
||||
<span style="font-weight: normal" t-field="value_id.name"/>
|
||||
</label>
|
||||
</li>
|
||||
|
@ -515,7 +531,7 @@
|
|||
</li>
|
||||
<li t-if="attribute_id.type == 'float' and attribute_id.float_min != attribute_id.float_max">
|
||||
<div t-field="attribute_id.name"/>
|
||||
<t t-set="attribute" t-value="Ecommerce.has_search_attributes(attribute_id.id)"/>
|
||||
<t t-set="attribute" t-value="Ecommerce.has_search_filter(attribute_id.id)"/>
|
||||
<div style="margin: 0 20px;" class="js_slider"
|
||||
t-att-data-id="attribute_id.id"
|
||||
t-att-data-value-min="attribute and attribute[1][0] or attribute_id.float_min"
|
||||
|
@ -523,14 +539,22 @@
|
|||
t-att-data-min="attribute_id.float_min"
|
||||
t-att-data-max="attribute_id.float_max"></div>
|
||||
</li>
|
||||
</t>
|
||||
</t>
|
||||
</ul>
|
||||
<button class="btn btn-xs btn-primary mt16">Apply filter</button>
|
||||
<a t-href="/shop/" t-keep-query="category,search,add_filter" class="btn btn-xs btn-default mt16">Cancel filter</a>
|
||||
</form>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='products_grid_before']" position="attributes">
|
||||
<attribute name="class">col-md-3</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='products_grid']" position="attributes">
|
||||
<attribute name="class">col-md-9</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in list view">
|
||||
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in my cart">
|
||||
<xpath expr="//table[@id='mycart_products']" position="after">
|
||||
<table t-if="suggested_products" class='table table-striped table-condensed'>
|
||||
<colgroup>
|
||||
|
@ -619,7 +643,7 @@
|
|||
<div class="col-md-8 oe_mycart">
|
||||
<h3 class="page-header mt16">Billing Information
|
||||
<small t-if="user_id.id == website.public_user.id"> or
|
||||
<a t-if="not partner" t-attf-href="/web#action=redirect&url=#{ request.httprequest.host_url }/shop/checkout/">sign in</a>
|
||||
<a t-if="not partner" t-attf-href="/web#action=redirect&url=#{ request.httprequest.url }">sign in</a>
|
||||
</small>
|
||||
</h3>
|
||||
<div class="row">
|
||||
|
@ -636,7 +660,7 @@
|
|||
<input type="email" name="email" class="form-control" t-att-value="checkout.get('email')"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{ error.get('phone') and 'has-error' or ''} col-lg-6">
|
||||
<label class="control-label" for="phone">Telephone</label>
|
||||
<label class="control-label" for="phone">Phone</label>
|
||||
<input type="tel" name="phone" class="form-control" t-att-value="checkout.get('phone')"/>
|
||||
</div>
|
||||
|
||||
|
@ -692,7 +716,7 @@
|
|||
<input type="text" name="shipping_name" class="form-control" t-att-value="checkout.get('shipping_name', '')"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('shipping_phone') and 'has-error' or ''} col-lg-6">
|
||||
<label class="control-label" for="contact_name">Telephone</label>
|
||||
<label class="control-label" for="contact_name">Phone</label>
|
||||
<input type="tel" name="shipping_phone" class="form-control" t-att-value="checkout.get('shipping_phone', '')"/>
|
||||
</div>
|
||||
<div t-attf-class="form-group #{error.get('shipping_street') and 'has-error' or ''} col-lg-6">
|
||||
|
@ -876,5 +900,12 @@
|
|||
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="total">
|
||||
<span t-field="website_sale_order.amount_total" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"display_currency": "website.pricelist_id.currency_id"
|
||||
}'/>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<field name="website_published"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@string='Information']" position="inside">
|
||||
<group colspan="4" string="Products On Ecommerce">
|
||||
<group colspan="4" string="Website Options">
|
||||
<field name="suggested_product_ids" widget="many2many_tags"/>
|
||||
<field name="website_style_ids" widget="many2many_tags"/>
|
||||
<field colspan="4" name="website_attribute_ids" nolabel="1">
|
||||
|
@ -81,9 +81,22 @@
|
|||
</group>
|
||||
</xpath>
|
||||
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_product_attribute_form">
|
||||
<field name="name">product.attribute.form</field>
|
||||
<field name="model">product.attribute</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product Attributes" version="7.0">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="visible"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="menu_shop" model="website.menu">
|
||||
<field name="name">Shop</field>
|
||||
<field name="url">/shop</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">30</field>
|
||||
</record>
|
||||
<record id="action_open_website" model="ir.actions.act_url">
|
||||
<field name="name">Website Shop</field>
|
||||
<field name="target">self</field>
|
||||
|
|
Loading…
Reference in New Issue