diff --git a/src/templates/_macros/_navigation.pug b/src/templates/_macros/_navigation.pug new file mode 100644 index 0000000..46290fe --- /dev/null +++ b/src/templates/_macros/_navigation.pug @@ -0,0 +1,26 @@ +include ../mixins/components + +| {% macro navigation_tabs(title) %} ++nav-secondary() + +nav-secondary-link( + class="{% if title == 'homepage' %}active{% endif %}", + href="{{ url_for('main.homepage') }}") + | Activity + + +nav-secondary-link( + class="{% if title == 'home' %}active{% endif %}", + href="{{ url_for('projects.home_project') }}") + | Home + + +nav-secondary-link( + class="{% if title == 'dashboard' %}active{% endif %}", + href="{{ url_for('projects.index') }}") + | My Projects + + | {% if current_user.has_organizations() %} + +nav-secondary-link( + class="{% if title == 'organizations' %}active{% endif %}", + href="{{ url_for('pillar.web.organizations.index') }}") + | My Organizations + | {% endif %} +| {% endmacro %} diff --git a/src/templates/nodes/custom/blog/_macros.pug b/src/templates/nodes/custom/blog/_macros.pug new file mode 100644 index 0000000..84ec904 --- /dev/null +++ b/src/templates/nodes/custom/blog/_macros.pug @@ -0,0 +1,164 @@ +include ../../../mixins/components + +| {% import 'projects/_macros.html' as projectmacros %} +| {% macro render_blog_post(node, project=None, pages=None) %} + +.expand-image-links.imgs-fluid + | {% if node.picture %} + +jumbotron( + "{{ node.name }}", + "{{ node._created | pretty_date }}", + "{{ node.picture.thumbnail('h', api=api) }}", + "{{ node.url }}")(class="row") + | {% else %} + .pt-3.text-center.text-muted + h2 + a.text-muted(href="{{ node.url }}") + | {{ node.name }} + ul.d-flex.list-unstyled.justify-content-center + | {% if node.project.name %} + li.pr-2 {{ node.project.name }} + | {% endif %} + | {% if node.user.full_name %} + li.pr-2 + | {{ node.user.full_name }} + | {% endif %} + li + a.px-2.text-muted(href="{{ node.url }}", + title="Updated {{ node._updated | pretty_date }}") + | {{ node._created | pretty_date }} + li + a.px-2(href="{{ node.url }}#comments") + | Leave a comment + + | {% if node.has_method('PUT') %} + li + a.px-2(href="{{url_for('nodes.edit', node_id=node._id)}}") + i.pi-edit + | Edit Post + | {% endif %} + | {% endif %} + + | {% if project and project._id != config.MAIN_PROJECT_ID %} + | {{ projectmacros.render_secondary_navigation(project, pages=pages) }} + | {% endif %} + + .row + .col-md-9.mx-auto + + .item-content.pt-4 + | {{ node.properties | markdowned('content') }} + + hr.my-4 +| {% endmacro %} + +//- ******************************************************* -// +| {% macro render_blog_list_item(node) %} +.row.position-relative.py-2 + .col-md-1 + | {% if node.picture %} + a.imgs-fluid(href="{{ node.url }}") + img(src="{{ node.picture.thumbnail('s', api=api) }}") + | {% else %} + .bg-primary.rounded.h-100 + a.d-flex.align-items-center.justify-content-center.h-100.text-white(href="{{ node.url }}") + i.pi-document-text + | {% endif %} + + .col-md-11 + h5 + a.text-muted(href="{{ node.url }}") {{node.name}} + + .text-muted. + #[span(title="{{node._created}}") {{node._created | pretty_date }}] + {% if node._created != node._updated %} + #[span(title="{{node._updated}}") (updated {{node._updated | pretty_date }})] + {% endif %} + {% if node.properties.category %} · {{node.properties.category}}{% endif %} + · {{node.user.full_name}} + {% if node.properties.status != 'published' %} · {{ node.properties.status}} {% endif %} + +| {% endmacro %} + + +//- ******************************************************* -// +| {% macro render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %} +| {% if can_create_blog_posts %} ++nav-secondary + +nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}") + span.text-success + i.pi-plus + | Create New Blog Post +| {% endif %} + +| {% if posts %} +| {{ render_blog_post(posts[0], project=project, pages=pages) }} + +.container + .row + .col-md-9.mx-auto + | {% for node in posts[1:] %} + | {% if loop.first %} + h5.text-muted.text-center Blasts from the past + | {% endif %} + | {{ render_blog_list_item(node) }} + | {% endfor %} + + | {% if more_posts_available %} + .blog-archive-navigation + a(href="{{ project.blog_archive_url }}") + | {{posts_meta.total - posts|length}} more blog posts over here + i.pi-angle-right + | {% endif %} + +| {% else %} + +.text-center + p No posts... yet! + +| {% endif %} {# posts #} +| {% endmacro %} + + +//- Macro for rendering the navigation buttons for prev/next pages -// +| {% macro render_archive_pagination(project) %} +.blog-archive-navigation + | {% if project.blog_archive_prev %} + a.archive-nav-button( + href="{{ project.blog_archive_prev }}", rel="prev") + i.pi-angle-left + | Previous page + | {% else %} + span.archive-nav-button + i.pi-angle-left + | Previous page + | {% endif %} + + a.archive-nav-button( + href="{{ url_for('main.project_blog', project_url=project.url) }}") + | Blog Index + + | {% if project.blog_archive_next %} + a.archive-nav-button( + href="{{ project.blog_archive_next }}", rel="next") + | Next page + i.pi-angle-right + | {% else %} + span.archive-nav-button + | Next page + i.pi-angle-right + | {% endif %} + +| {% endmacro %} + +| {% macro render_archive(project, posts, posts_meta) %} + +| {{ render_archive_pagination(project) }} + +| {% for node in posts %} +| {{ render_blog_list_item(node) }} +| {% endfor %} + +| {{ render_archive_pagination(project) }} + +| {% endmacro %} diff --git a/src/templates/organizations/index.pug b/src/templates/organizations/index.pug new file mode 100644 index 0000000..4c3f6eb --- /dev/null +++ b/src/templates/organizations/index.pug @@ -0,0 +1,213 @@ +| {% extends 'layout.html' %} +| {% from '_macros/_navigation.html' import navigation_tabs %} +include ../mixins/components + +| {% set title = 'organizations' %} +| {% block page_title %}Organizations{% endblock %} + +| {% block og %} +meta(property="og:title", content="Dashboard") +meta(name="twitter:title", content="Blender Cloud") + +meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}") +meta(property="og:type", content="website") + +meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +| {% endblock %} + + +| {% block navigation_tabs %} +| {{ navigation_tabs(title) }} +| {% endblock navigation_tabs %} + +| {% block body %} ++nav-secondary + | {% if can_create_organization %} + +nav-secondary-link( + class="create", + onclick='createNewOrganization(this)') + span.text-success + i.pi-plus + | Create Organization + | {% endif %} + + li#create_organization_result_panel.result + +.container-fluid.dashboard-container + .row + .col-md-6 + ul.projects__list + | {% if organizations %} + | {% for organization in organizations['_items'] %} + | {% set link_url = url_for('pillar.web.organizations.view_embed', organization_id=organization._id) %} + li.projects__list-item( + data-url="{{ link_url }}", + id="organization-{{ organization._id }}") + a.projects__list-thumbnail( + href="{{ link_url }}") + i.pi-users + .projects__list-details + a.title(href="{{ link_url }}") + | {{ organization.name }} + + ul.meta + li(title="Members") + | {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }} + | {% if (organization.unknown_members|count) != 0 %} + | ({{ organization.unknown_members|hide_none|count }} pending) + | {% endif %} + li(title="Seats") + | {{ organization.seat_count }} Seat{{ organization.seat_count|pluralize }} + + | {% endfor %} + | {% else %} + li.projects__list-item + a.projects__list-thumbnail + i.pi-blender-cloud + .projects__list-details + span Create an Organization to get started! + | {% endif %} + + .col-md-6.py-1.pb-3 + #item-details + +| {% endblock %} + + +| {% block footer_scripts %} +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}") +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.autocomplete-0.22.0.min.js') }}", async=true) + +script. + + /* Returns a more-or-less reasonable message given an error response object. */ + function xhrErrorResponseMessage(err) { + if (typeof err.responseJSON == 'undefined') + return err.statusText; + + if (typeof err.responseJSON._error != 'undefined' && typeof err.responseJSON._error.message != 'undefined') + return err.responseJSON._error.message; + + if (typeof err.responseJSON._message != 'undefined') + return err.responseJSON._message + + return err.statusText; + } + + /** + * Open an organization in the #item-details div. + */ + function item_open(item_id, pushState) + { + if (item_id === undefined ) { + throw new ReferenceError("item_open(" + item_id + ") called."); + } + + // Style elements starting with item_type and dash, e.g. "#job-uuid" + var clean_classes = 'active processing'; + var current_item = $('#organization-' + item_id); + + $('[id^="organization-"]').removeClass(clean_classes); + current_item + .removeClass(clean_classes) + .addClass('processing'); + + var item_url = '/o/' + item_id; + + $.get(item_url, function(item_data) { + $('#item-details').html(item_data); + + current_item + .removeClass(clean_classes) + .addClass('active'); + + }).fail(function(xhr) { + if (console) { + console.log('Error fetching organization', item_id, 'from', item_url); + console.log('XHR:', xhr); + } + + current_item.removeClass(clean_classes); + toastr.error('Failed to open organization'); + + if (xhr.status) { + $('#item-details').html(xhr.responseText); + } else { + $('#item-details').html('

Opening ' + item_type + ' failed. There possibly was ' + + 'an error connecting to the server. Please check your network connection and ' + + 'try again.

'); + } + }); + + // Determine whether we should push the new state or not. + pushState = (typeof pushState !== 'undefined') ? pushState : true; + if (!pushState) return; + + // Push the correct URL onto the history. + var push_state = {itemId: item_id}; + + window.history.pushState( + push_state, + 'Organization: ' + item_id, + item_url + ); + } + + $('li.projects__list-item').click(function(e){ + url = $(this).data('url'); + if (typeof url === 'undefined') return; + + window.location.href = url; + if (console) console.log(url); + + $(this).addClass('active'); + $(this).find('.projects__list-thumbnail i') + .removeAttr('class') + .addClass('pi-spin spin'); + }); + + + {% if open_organization_id %} + $(function() { item_open('{{ open_organization_id }}', false); }); + {% endif %} + + {% if can_create_organization %} + function createNewOrganization(button) { + $(button) + .attr('disabled', 'disabled') + .fadeTo(200, 0.1); + $('#create_organization_result_panel').html(''); + + // TODO: create a form to get the initial info from the user. + $.post( + '{{ url_for('pillar.web.organizations.create_new') }}', + { + name: 'New Organization', + seat_count: 1, + } + ) + .done(function(result) { + var $p = $('

').text('organization created, reloading list.') + $('#create_organization_result_panel').html($p); + + window.location.href = result.location; + }) + .fail(function(err) { + var msg = xhrErrorResponseMessage(err); + $('#create_organization_result_panel').html('Error creating organization: ' + msg); + + $(button) + .fadeTo(1000, 1.0) + .queue(function() { + $(this) + .removeAttr('disabled') + .dequeue() + ; + }) + }) + ; + return false; + } + {% endif %} +| {% endblock %} diff --git a/src/templates/projects/home_index.pug b/src/templates/projects/home_index.pug new file mode 100644 index 0000000..610ff0d --- /dev/null +++ b/src/templates/projects/home_index.pug @@ -0,0 +1,59 @@ +| {% extends 'projects/home_layout.html' %} +| {% set subtab = 'blender_sync' %} +| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %} +| {% block currenttab %} +.container-fluid + section.nav-tabs__tab.active#tab-blender_sync + .tab_header-container + .tab_header-intro( + style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})") + .tab_header-intro_text + h2 Connect Blender with the Cloud + p + | Save your Blender preferences and keymaps once, load them anywhere. +
+ | Use the + =' ' + a(href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') Blender Cloud add-on + =' ' + | to synchronise your settings from within Blender. + + | {% if show_addon_download_buttons %} + .row + .col-md-6 + a.btn.btn-block.btn-outline-success( + href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip") + i.pi-download + | Download v{{ config.BLENDER_CLOUD_ADDON_VERSION }} + .col-md-6 + a.btn.btn-link( + href="{{ learn_more_btn_url }}") + | Learn More + i.pi-angle-right + | {% endif %} + + .tab_header-intro_icons + i.pi-blender + i.pi-heart-filled + i.pi-blender-cloud + + | {% for version in synced_versions %} + .blender_sync-main + .blender_sync-main-header + h5.blender_sync-main-title + i.pi-blender + | Blender {{ version.version }} + .blender_sync-main-last + | Last synced on: {{ version.date|pretty_date }} + | {% else %} + .blender_sync-main.empty + .blender_sync-main-header + span.blender_sync-main-title + | No settings synced yet +


+ a.download( + href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') + | Download add-on + | {% endfor %} +| {% endblock %} + diff --git a/src/templates/projects/home_layout.pug b/src/templates/projects/home_layout.pug new file mode 100644 index 0000000..454f508 --- /dev/null +++ b/src/templates/projects/home_layout.pug @@ -0,0 +1,51 @@ +| {% extends 'layout.html' %} +| {% from '_macros/_navigation.html' import navigation_tabs %} +include ../mixins/components + +| {% set title = 'home' %} + +| {% block og %} +meta(property="og:type", content="website") +meta(property="og:url", content="https://cloud.blender.org{{ request.path }}") + +meta(property="og:title", content="Blender Cloud - Home") +meta(name="twitter:title", content="Blender Cloud") + +meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +| {% endblock %} + +| {% block page_title %} +| {{current_user.full_name}} +| {% endblock %} + +| {% block navigation_tabs %} +| {{ navigation_tabs(title) }} +| {% endblock navigation_tabs %} + +| {% block body %} +.dashboard-container + + section#projects.bg-white + +nav-secondary()(id='sub-nav-tabs__list') + +nav-secondary-link(id="subtab-blender_sync", data-tab-url="{{ url_for('projects.home_project')}}") + | Blender Sync + + +nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}") + | Images + + | {% block currenttab %}{% endblock %} +| {% endblock %} + +| {% block footer_scripts %} +script. + $(document).ready(function () { + $('#subtab-{{ subtab }}').addClass('active'); + + var $nav_tabs = $('#sub-nav-tabs__list').find('a.nav-link'); + $nav_tabs.on('click', function (e) { + console.log($(this)); + window.location = $(this).attr('data-tab-url'); + }); + }); +| {% endblock %} diff --git a/src/templates/projects/index_dashboard.pug b/src/templates/projects/index_dashboard.pug new file mode 100644 index 0000000..4297dd1 --- /dev/null +++ b/src/templates/projects/index_dashboard.pug @@ -0,0 +1,300 @@ +| {% extends 'layout.html' %} +| {% from '_macros/_navigation.html' import navigation_tabs %} +include ../mixins/components + +| {% set title = 'dashboard' %} + +| {% block og %} +meta(property="og:title", content="Dashboard") +meta(name="twitter:title", content="Blender Cloud") + +meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}") +meta(property="og:type", content="website") + +meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}") +| {% endblock %} + +| {% block page_title %} +| {{current_user.full_name}} +| {% endblock %} + +| {% block css %} +| {{ super() }} +style. + .deleted-projects-toggle { + z-index: 10; + position: absolute; + right: 0; + font-size: 20px; + padding: 3px; + text-shadow: 0 0 2px white; + } + .deleted-projects-toggle .show-deleted { + color: #aaa; + } + .deleted-projects-toggle .hide-deleted { + color: #bbb; + } +| {% endblock %} + +| {% block navigation_tabs %} +| {{ navigation_tabs(title) }} +| {% endblock navigation_tabs %} + + +| {% block body %} +.dashboard-container + section.dashboard-main + section#projects.bg-white + +nav-secondary()(id='sub-nav-tabs__list') + +nav-secondary-link(data-tab-toggle='own_projects', class="active") + | Own Projects + | {% if projects_user|length != 0 %} + span ({{ projects_user|length }}) + | {% endif %} + + +nav-secondary-link(data-tab-toggle='shared') + | Shared with me + | {% if projects_shared|length != 0 %} + span ({{ projects_shared|length }}) + | {% endif %} + + | {% if current_user.has_cap('subscriber') %} + +nav-secondary-link( + id="project-create", + data-url="{{ url_for('projects.create') }}", + href="{{ url_for('projects.create') }}") + span.text-success + | #[i.pi-plus] Create New Project + | {% elif current_user.has_cap('can-renew-subscription') %} + +nav-secondary-link( + id="project-create", + data-url="{{ url_for('projects.create') }}", + href="/renew", + target="_blank") + | #[i.pi-heart-filled.text-danger] Resubscribe to Create a Project + | {% endif %} + + nav.nav-tabs__tab.active#own_projects + .deleted-projects-toggle + | {% if show_deleted_projects %} + a.hide-deleted(href="{{ request.base_url }}", title='Hide deleted projects') + i.pi-trash + | {% else %} + a.show-deleted(href="{{ request.base_url }}?deleted=1", title='Show deleted projects') + i.pi-trash + | {% endif %} + + ul.projects__list + | {% for project in projects_deleted %} + li.projects__list-item.deleted + span.projects__list-thumbnail + | {% if project.picture_square %} + img(src="{{ project.picture_square.thumbnail('s', api=api) }}") + | {% else %} + i.pi-blender-cloud + | {% endif %} + .projects__list-details + span.title {{ project.name }} + ul.meta + li.status.deleted Deleted + li.edit + a(href="javascript:undelete_project('{{ project._id }}')") Restore project + | {% else %} + | {% if show_deleted_projects %} + li.projects__list-item.deleted You have no recenly deleted projects. Deleted projects can be restored within a month after deletion. + | {% endif %} + | {% endfor %} + + | {% for project in projects_user %} + li.projects__list-item( + data-url="{{ url_for('projects.view', project_url=project.url) }}") + a.projects__list-thumbnail( + href="{{ url_for('projects.view', project_url=project.url) }}") + | {% if project.picture_square %} + img(src="{{ project.picture_square.thumbnail('s', api=api) }}") + | {% else %} + i.pi-blender-cloud + | {% endif %} + .projects__list-details + a.title(href="{{ url_for('projects.view', project_url=project.url) }}") + | {{ project.name }} + + ul.meta + li.status( + class="{{ project.is_private | yesno('private,public,') }}", + title="{{ project.is_private | yesno('Private Project,Public Project,') }}") + | {{ project.is_private | yesno('Private,Public,') }} + li.when(title="{{ project._created }}") {{ project._created | pretty_date }} + li.edit + a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit + | {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %} + li.pending Not Published + | {% endif %} + | {% else %} + | {% if current_user.has_cap('subscriber') %} + li.projects__list-item(data-url="{{ url_for('projects.create') }}") + a.projects__list-thumbnail + i.pi-plus + .projects__list-details + a.title(href="{{ url_for('projects.create') }}") + | Create a project to get started! + | {% elif current_user.has_cap('can-renew-subscription') %} + li.projects__list-item(data-url="https://store.blender.org/renew-my-subscription.php") + a.projects__list-thumbnail + i.pi-plus + .projects__list-details + a.title(href="https://store.blender.org/renew-my-subscription.php") + | Renew your Blender Cloud subscription to create your own projects! + | {% else %} + li.projects__list-item(data-url="/join") + a.projects__list-thumbnail + i.pi-plus + .projects__list-details + a.title(href="/join") + | Join Blender Cloud to create your own projects! + | {% endif %} + | {% endfor %} + + section.nav-tabs__tab#shared(style='display: none') + ul.projects__list + | {% if projects_shared %} + | {% for project in projects_shared %} + li.projects__list-item( + data-url="{{ url_for('projects.view', project_url=project.url) }}") + a.projects__list-thumbnail( + href="{{ url_for('projects.view', project_url=project.url) }}") + | {% if project.picture_square %} + img(src="{{ project.picture_square.thumbnail('s', api=api) }}") + | {% else %} + i.pi-blender-cloud + | {% endif %} + .projects__list-details + a.title(href="{{ url_for('projects.view', project_url=project.url) }}") + | {{ project.name }} + + ul.meta + li.status( + class="{{ project.is_private | yesno('private,public,') }}", + title="{{ project.is_private | yesno('Private Project,Public Project,') }}") + | {{ project.is_private | yesno('Private,Public,') }} + li.when {{ project._created | pretty_date }} + li.who by {{ project.user.full_name }} + li.edit + a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit + | {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %} + li.pending Not Published + | {% endif %} + + li.leave + span.user-remove-prompt + | Leave Project + + span.user-remove + | Are you sure? + span.user-remove-confirm( + user-id="{{ current_user.objectid }}", + project-url="{{url_for('projects.sharing', project_url=project.url)}}") + i.pi-check + | Yes, leave + span.user-remove-cancel + i.pi-cancel + | No, cancel + + | {% endfor %} + | {% else %} + li.projects__list-item + a.projects__list-thumbnail + i.pi-heart-broken + .projects__list-details + .title + | No projects shared with you... yet! + | {% endif %} +| {% endblock %} + + +| {% block footer_scripts %} +script. + $(document).ready(function() { + + $('li.projects__list-item').click(function(e){ + url = $(this).data('url'); + if (typeof url === 'undefined') return; + + window.location.href = url; + if (console) console.log(url); + + $(this).addClass('active'); + $(this).find('.projects__list-thumbnail i') + .removeAttr('class') + .addClass('pi-spin spin'); + }); + + // Tabs behavior + var $nav_tabs_list = $('#sub-nav-tabs__list'); + var $nav_tabs = $nav_tabs_list.find('a.nav-link'); + $nav_tabs.on('click', function(e){ + e.preventDefault(); + + $nav_tabs.removeClass('active'); + $(this).addClass('active'); + + $('.nav-tabs__tab').hide(); + $('#' + $(this).attr('data-tab-toggle')).show(); + }); + + // Leave project + var $projects_list = $('ul.projects__list'); + $projects_list.find('span.user-remove-prompt').on('click', function(e){ + e.stopPropagation(); + e.preventDefault(); + + $(this).next().show(); + $(this).hide(); + }); + + $projects_list.find('span.user-remove-cancel').on('click', function(e){ + e.stopPropagation(); + e.preventDefault(); + + $(this).parent().prev().show(); + $(this).parent().hide(); + }); + + $projects_list.find('span.user-remove-confirm').on('click', function(e){ + e.stopPropagation(); + e.preventDefault(); + var parent = $(this).closest('.projects__list-item'); + + function removeUser(userId, projectUrl){ + $.post(projectUrl, {user_id: userId, action: 'remove'}) + .done(function (data) { + parent.remove(); + }); + } + + removeUser($(this).attr('user-id'), $(this).attr('project-url')); + }); + + hopToTop(); // Display jump to top button + }); + + + var patch_url = '{{ url_for('projects.patch.patch_project', project_id='PROJECTID') }}'; + function undelete_project(project_id) { + console.log('undeleting project', project_id); + $.ajax({ + url: patch_url.replace('PROJECTID', project_id), + method: 'PATCH', + data: JSON.stringify({'op': 'undelete'}), + contentType: 'application/json' + }) + .done(function(data, textStatus, jqXHR) { + location.href = jqXHR.getResponseHeader('Location'); + }) + .fail(function(err) { + toastr.error(xhrErrorResponseMessage(err), 'Undeletion failed'); + }) + } +| {% endblock %} diff --git a/src/templates/projects/view.pug b/src/templates/projects/view.pug new file mode 100644 index 0000000..709e080 --- /dev/null +++ b/src/templates/projects/view.pug @@ -0,0 +1,687 @@ +| {% extends 'layout.html' %} +| {% from '_macros/_add_new_menu.html' import add_new_menu %} +include ../mixins/components + +| {% block page_title %}{{ project.name }}{% endblock%} +| {% set title = 'project' %} + +| {% block og %} +meta(property="og:type", content="website") + +| {% if og_picture %} +meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}") +meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}") +| {% elif node and node.picture %} +meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}") +meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}") +| {% elif project.picture_header %} +meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}") +meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}") +| {% endif %} + +| {% if show_project %} +meta(property="og:title", content="{{ project.name }} - Blender Cloud") +meta(name="twitter:title", content="{{ project.name }} - Blender Cloud") +meta(property="og:description", content="{{ project.summary }}") +meta(name="twitter:description", content="{{ project.summary }}") + +meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}") +| {% else %} + +| {% if node %} +meta(property="og:title", content="{{ node.name }} - Blender Cloud") +meta(name="twitter:title", content="{{ node.name }} on Blender Cloud") + +| {% if node.node_type == 'post' %} + +| {% if node.properties.content %} +meta(property="og:description", content="{{ node.properties.content | truncate(180) }}") +meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}") +| {% else %} +meta(property="og:description", content="Blender Cloud, your source for open content and training") +meta(name="twitter:description", content="Blender Cloud, your source for open content and training") +| {% endif %} + +| {% else %} + +| {% if node.description %} +meta(property="og:description", content="{{ node.description | truncate(180) }}") +meta(name="twitter:description", content="{{ node.description | truncate(180) }}") +| {% else %} +meta(property="og:description", content="Blender Cloud, your source for open content and training") +meta(name="twitter:description", content="Blender Cloud, your source for open content and training") +| {% endif %} + +| {% endif %} + +meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}") +| {% else %} +meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud") +meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud") +meta(property="og:description", content="{{ project.summary }}") +meta(name="twitter:description", content="{{ project.summary }}") + +meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}") +| {% endif %} + +| {% endif %} +| {% endblock %} + +| {% block head %} +link(href="{{ url_for('static_pillar', filename='assets/jstree/themes/default/style.min.css') }}", rel="stylesheet") +| {% if node %} +link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}") +| {% endif %} + +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}") +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}") +script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}") +| {% endblock %} + +| {% block css %} +link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet") +link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet") +| {% endblock %} + +| {% block navigation_tabs %} ++nav-secondary()(class="bg-white") + | {% if project.category == 'course' %} + li.text-capitalize + a.nav-link.text-muted.px-0(href="{{ url_for('cloud.courses') }}") + | Courses + | {% elif project.category == 'workshop' %} + li.text-capitalize + a.nav-link.text-muted.px-0(href="{{ url_for('cloud.workshops') }}") + | Workshops + li.px-1 + i.pi-angle-right + | {% endif %} + + +nav-secondary-link( + class="px-0", + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | {{ project.name }} + + | {% if project.category == "open_project" %} + +nav-secondary-link( + class="active", + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | Explore + +nav-secondary-link( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | Blog + +nav-secondary-link( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | About + +nav-secondary-link( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | Team + +nav-secondary-link( + href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + | Awards + | {% endif %} +| {% endblock navigation_tabs %} + +| {% block body %} +#project-container + #project-side-container + #project_sidebar.bg-white + ul.project-tabs.p-0 + //- li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}") + //- a(href="{{url_for('projects.view', project_url=project.url)}}") + //- #project-loading + //- i.pi-spin + //- | {% if project.picture_square %} + //- img(src="{{ project.picture_square.thumbnail('b', api=api) }}") + //- | {% else %} + //- i.pi-home + //- | {% endif %} + + li.tabs-browse( + title="Browse", + data-toggle="tooltip", + data-placement="right", + class="active") + a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + i.pi-folder + + | {% if not project.is_private %} + | {% if current_user_is_subscriber %} + li.tabs-search( + title="Search", + data-toggle="tooltip", + data-placement="right") + a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ") + i.pi-search + | {% else %} + li.tabs-search( + title="Search (subscribers only)", + data-toggle="tooltip", + data-placement="right") + a(href="{{ url_for('cloud.join') }}") + i.pi-search + | {% endif %} + | {% endif %} + | {{ extension_sidebar_links }} + + | {% if project.has_method('PUT') %} + li( + title="Edit Project", + data-toggle="tooltip", + data-placement="right") + a(href="{{ url_for('projects.edit', project_url=project.url) }}") + i.pi-cog + | {% endif %} + + + #project_nav(class="{{ title }}") + #project_nav-container + | {% if title != 'about' %} + //- +nav-secondary(class="bg-white") + //- +nav-secondary-link( + //- class="active", + //- href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + //- | {{ project.name }} + //- #project_nav-header.bg-white + + //- a.project-title.p-2.font-weight-bold.text-dark( + //- href="{{url_for('projects.view', project_url=project.url, _external=True)}}") + //- | {{ project.name }} + + | {% block project_tree %} + #project_tree.bg-white + | {% endblock project_tree %} + | {% endif %} + + + #project_context-container.border-left + | {% if project.has_method('PUT') %} + #project_context-header.bg-white + span#status-bar + + ul.project-edit-tools.disabled + li.dropdown + button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle( + type="button", + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false") + i.button-add-icon.pi-collection-plus + | New... + + ul.dropdown-menu.add_new-menu + | {{ add_new_menu(project.node_types) }} + + li.button-edit + a#item_edit.project-mode-view.btn.btn-sm.btn-outline-secondary.ml-2( + href="javascript:void(0);", + title="Edit", + data-project_id="{{project._id}}") + i.button-edit-icon.pi-edit + | Edit Project + + li.dropdown + button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2( + type="button", + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false") + i.pi-more-vertical.p-0 + + ul.dropdown-menu + | {% if current_user.has_cap('admin') %} + li.dropdown-item + a#item_featured( + href="javascript:void(0);", + title="Feature on project's homepage", + data-toggle="tooltip", + data-placement="left") + i.pi-star + | Toggle Featured + + li.dropdown-item + a#item_toggle_public( + href="javascript:void(0);", + title="Make it accessible to anyone", + data-toggle="tooltip", + data-placement="left") + i.pi-lock-open + | Toggle Public + | {% endif %} + + li.dropdown-item + a#item_toggle_projheader( + href="javascript:void(0);", + title="Feature as project's header", + data-toggle="tooltip", + data-placement="left") + i.pi-star + | Toggle Project Header video + + li.dropdown-item.button-move + a#item_move( + href="javascript:void(0);", + title="Move into a folder...", + data-toggle="tooltip", + data-placement="left") + i.button-move-icon.pi-move + | Move + + li.dropdown-item.button-delete + a#item_delete( + href="javascript:void(0);", + title="Can be undone within a month", + data-toggle="tooltip", + data-placement="left") + i.pi-trash + | Delete Project + + // Edit Mode + li.button-cancel + a#item_cancel.project-mode-edit.btn.btn-outline-secondary( + href="javascript:void(0);", + title="Cancel changes") + i.button-cancel-icon.pi-cancel + | Cancel + + li.button-save + a#item_save.project-mode-edit.btn.btn-outline-success.mx-2( + href="javascript:void(0);", + title="Save changes") + i.button-save-icon.pi-check + | Save Changes + + | {% endif %} + + | {% set utm_source = request.args.get('utm_source') %} + | {% if config.UTM_LINKS and utm_source in config.UTM_LINKS %} + #utm_container + a(href="{{config.UTM_LINKS[utm_source]['link']}}") + img(src="{{config.UTM_LINKS[utm_source]['image']}}", alt="gift", class="img-responsive") + | {% endif %} + #project_context + | {% block project_context %} + | {% if show_project %} + | {% include "projects/view_embed.html" %} + | {% endif %} + | {% endblock project_context %} + + #overlay-mode-move-container + .overlay-container + .title + i.pi-angle-left + | Select the folder where you want to move it + .buttons + button#item_move_accept.move.disabled + | Select a Folder + button#item_move_cancel.cancel + i.pi-cancel + | Cancel + +| {% endblock %} + +| {% block footer_container %}{% endblock %} + +| {% block footer_scripts_pre %} + +| {% if project.has_method('PUT') %} +| {# JS containing the Edit, Add, Featured, and Move functions #} +script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets/js/project-edit.min.js') }}") +| {% endif %} + +script. + function updateToggleProjHeaderMenuItem() { + var $toggle_projheader = $('#item_toggle_projheader'); + + if (ProjectUtils.isProject()) { + $toggle_projheader.hide(); + return; + } + if (ProjectUtils.nodeType() == 'asset') { + $toggle_projheader.show(); + } else { + $toggle_projheader.hide(); + } + } + $(updateToggleProjHeaderMenuItem); + + // Function to update the interface on loadNodeContent, and edit/saving assets + function updateUi(nodeId, mode) { + + if (mode === 'view') { + $('.project-mode-view').displayAs('inline-block'); + $('.project-mode-edit').hide(); + + $("#node-edit-form").unbind("submit"); + $("#item_save").unbind("click"); + $("#item_cancel").unbind("click"); + } else if (mode === 'edit') { + $('.project-mode-view').hide(); + $('.project-mode-edit').displayAs('inline-block'); + } else { + if (console) console.log('Invalid mode:', mode); + } + + // Prevent flicker by scrolling to top. + $("#project_context-container").scrollTop(0); + + // Enable specific items under the Add New dropdown + if (ProjectUtils.nodeType() === 'group') { + addMenuEnable(['asset', 'group']); + + } else if (ProjectUtils.nodeType() === 'group_texture') { + addMenuEnable(['group_texture', 'texture']); + + } else if (ProjectUtils.nodeType() === 'group_hdri') { + addMenuEnable(['group_hdri', 'hdri']); + + } else if (!ProjectUtils.isProject()) { + addMenuEnable(false); + } + + updateToggleProjHeaderMenuItem(); + + // Set the page title on the document + var page_title = $('#node-title').text() + " - {{ project.name }} — Blender Cloud"; + DocumentTitleAPI.set_page_title(page_title); + + // TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check + // it's done like that in all users of updateUi(). + $('#project-loading').removeAttr('class'); + } +| {% endblock %} + +| {% block footer_scripts %} +script(src="{{ url_for('static_pillar', filename='assets/jstree/jstree.min.js') }}") + +script. + {% if show_project %} + ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''}); + {% else %} + {% if node %} + ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: false, nodeId: '{{node._id}}'}); + {% endif %} + {% endif %} + + var projectTree = document.getElementById('project_tree'); + + var urlNodeMove = "{{url_for('projects.move_node')}}"; + var urlNodeFeature = "{{url_for('projects.add_featured_node')}}"; + var urlNodeDelete = "{{url_for('projects.delete_node')}}"; + var urlNodeTogglePublic = "{{url_for('projects.toggle_node_public')}}"; + var urlNodeToggleProjHeader = "{{url_for('projects.toggle_node_project_header')}}"; + var urlProjectDelete = "{{url_for('projects.delete')}}"; + var urlProjectEdit = "{{url_for('projects.edit', project_url=project.url)}}"; + + + function loadNodeContent(url, nodeId) { + $('#project-loading').addClass('active'); + $.get(url, function(dataHtml) { + // Update the DOM injecting the generate HTML into the page + $('#project_context').html(dataHtml); + }) + .done(function(){ + updateUi(nodeId, 'view'); + }) + .fail(function(dataResponse) { + $('#project_context').html($('