diff --git a/app/static/js/RessourceLists/PublicCorporaList.js b/app/static/js/RessourceLists/PublicCorporaList.js deleted file mode 100644 index 2171396eae562ebecc4aa4de8655560592ed7565..0000000000000000000000000000000000000000 --- a/app/static/js/RessourceLists/PublicCorporaList.js +++ /dev/null @@ -1,70 +0,0 @@ -class PublicCorporaList extends RessourceList { - static instances = []; - - static getInstance(elem) { - return PublicCorporaList.instances.find((instance) => { - return instance.listjs.list === elem; - }); - } - - static autoInit() { - for (let publicCorporaListElement of document.querySelectorAll('.public-corpora-list:not(.no-autoinit)')) { - new PublicCorporaList(publicCorporaListElement); - } - } - - static options = { - initialHtmlGenerator: (id) => { - return ` - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="${id}-search" class="search" type="search"></input> - <label for="${id}-search">Search corpus</label> - </div> - <table> - <thead> - <tr> - <th></th> - <th>Title</th> - <th>Description</th> - <th></th> - </tr> - </thead> - <tbody class="list"></tbody> - </table> - <ul class="pagination"></ul> - `.trim(); - }, - item: ` - <tr class="clickable hoverable"> - <td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td> - <td><b class="title"></b></td> - <td><i class="description"></i></td> - </tr> - `.trim(), - ressourceMapper: (corpus) => { - return { - 'id': corpus.id, - 'creation-date': corpus.creation_date, - 'description': corpus.description, - 'title': corpus.title - }; - }, - sortArgs: ['creation-date', {order: 'desc'}], - valueNames: [ - {data: ['id']}, - {data: ['creation-date']}, - 'description', - 'title' - ] - }; - - constructor(listElement, options = {}) { - super(listElement, {...PublicCorporaList.options, ...options}); - PublicCorporaList.instances.push(this); - } - - init(user) { - this._init(user.corpora.is_public); - } -} diff --git a/app/static/js/RessourceLists/RessourceList.js b/app/static/js/RessourceLists/RessourceList.js index 5af7a2319627d24d1e408d008e44c52d6c485d23..323f9d0e7b455e83fb04a43da03d919fecda6462 100644 --- a/app/static/js/RessourceLists/RessourceList.js +++ b/app/static/js/RessourceLists/RessourceList.js @@ -10,7 +10,6 @@ class RessourceList { JobList.autoInit(); JobInputList.autoInit(); JobResultList.autoInit(); - PublicCorporaList.autoInit(); SpaCyNLPPipelineModelList.autoInit(); TesseractOCRPipelineModelList.autoInit(); UserList.autoInit(); @@ -31,11 +30,11 @@ class RessourceList { ...{pagination: {item: `<li><a class="page" href="#${listElement.id}"></a></li>`}}, ...options } - if ('ressourceMapper' in options) { + if ('ressourceMapper' in options && typeof options.ressourceMapper === 'function') { this.ressourceMapper = options.ressourceMapper; delete options.ressourceMapper; } - if ('initialHtmlGenerator' in options) { + if ('initialHtmlGenerator' in options && typeof options.initialHtmlGenerator === 'function') { this.initialHtmlGenerator = options.initialHtmlGenerator; listElement.innerHTML = this.initialHtmlGenerator(listElement.id); delete options.initialHtmlGenerator; diff --git a/app/static/js/XMLtoObject.js b/app/static/js/XMLtoObject.js new file mode 100644 index 0000000000000000000000000000000000000000..001376cb7139308833fedf4665c509873fcea357 --- /dev/null +++ b/app/static/js/XMLtoObject.js @@ -0,0 +1,102 @@ +/** + * XMLtoObject - Converts XML into a JavaScript value or object. + * GitHub: https://github.com/Pevtrick/XMLtoObject + * by Patrick Jentsch: https://github.com/Pevtrick + */ + +/** + * The XMLDocument.toObject() method converts the XMLDocument into a JavaScript value or object. + * @param {String} [attributePrefix=] - A Prefix, which is added to all properties generated by XML attributes. + * @returns {Object} - The converted result. + */ + XMLDocument.prototype.toObject = function(attributePrefix='') { + let obj = {}; + + obj[this.documentElement.nodeName] = this.documentElement.toObject(attributePrefix); + + return obj; +}; + +/** +* The Node.toObject() method converts the Node into a JavaScript value or object. +* @param {String} [attributePrefix=] - A Prefix, which is added to all properties generated by XML attributes. +* @returns {Object|String|null} - The converted result. +*/ +Node.prototype.toObject = function(attributePrefix='') { + let obj = null; + + switch (this.nodeType) { + case Node.ELEMENT_NODE: + let hasAttributes = this.attributes.length > 0; + let hasChildNodes = this.childNodes.length > 0; + + /* Stop conversion if the Node doesn't contain any attributes or child nodes */ + if (!(hasAttributes || hasChildNodes)) { + break; + } + + obj = {}; + + /* Convert attributes */ + for (let attribute of this.attributes) { + obj[`attributePrefix${attribute.name}`] = attribute.value; + } + + /* Convert child nodes */ + for (let childNode of this.childNodes) { + switch (childNode.nodeType) { + case Node.ELEMENT_NODE: + break; + case Node.TEXT_NODE: + /* Check whether the child text node is the only child of the current node. */ + if (!hasAttributes && this.childNodes.length === 1) { + obj = childNode.toObject(attributePrefix); + continue; + } + if (childNode.data.trim() === '') {continue;} + break; + default: + /* This recursion leads to a console message. */ + childNode.toObject(attributePrefix); + continue; + } + /** + * If the child node is the first of its type in this childset, + * process it and add it directly as a property to the return object. + * If not add it to an array which is set as a property of the return object. + */ + if (childNode.nodeName in obj) { + if (!Array.isArray(obj[childNode.nodeName])) { + obj[childNode.nodeName] = [obj[childNode.nodeName]]; + } + obj[childNode.nodeName].push(childNode.toObject(attributePrefix)); + } else { + obj[childNode.nodeName] = childNode.toObject(attributePrefix); + } + } + break; + case Node.TEXT_NODE: + if (this.data.trim() !== '') {obj = this.data;} + break; + case Node.COMMENT_NODE: + console.log('Skipping comment node:'); + console.log(node); + break; + case Node.DOCUMENT_NODE: + obj = {}; + obj[this.documentElement.nodeName] = this.documentElement.toObject(attributePrefix); + break; + default: + /** + * The following node types are not processed because they don't offer data, which has to be stored in the object: + * Node.PROCESSING_INSTRUCTION_NODE, Node.DOCUMENT_TYPE_NODE, Node.DOCUMENT_FRAGMENT_NODE + * The following node types are deprecated and therefore not supported by this function: + * Node.ATTRIBUTE_NODE, Node.CDATA_SECTION_NODE, Node.ENTITY_REFERENCE_NODE, Node.ENTITY_NODE, Node.NOTATION_NODE + */ + console.log(`Node type: '${this.nodeType}' is not supported.`); + console.log(node); + break; + } + + return obj; +} diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 8ff90c330be20f1b379d824765a3b8cfb7f6f279..24d24d974bc7877efa90c7670149afa29498179b 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -24,10 +24,10 @@ 'js/RessourceLists/JobList.js', 'js/RessourceLists/JobInputList.js', 'js/RessourceLists/JobResultList.js', - 'js/RessourceLists/PublicCorporaList.js', 'js/RessourceLists/SpacyNLPPipelineModelList.js', 'js/RessourceLists/TesseractOCRPipelineModelList.js', - 'js/RessourceLists/UserList.js' + 'js/RessourceLists/UserList.js', + 'js/XMLtoObject.js' %} <script src="{{ ASSET_URL }}"></script> {%- endassets %} @@ -53,7 +53,7 @@ // Initialize components M.AutoInit(); - M.CharacterCounter.init(document.querySelectorAll('input[data-length][type="text"], input[data-length][type="email"], input[data-length][type="search"], input[data-length][type="password"], input[data-length][type="tel"], input[data-length][type="url"], textarea[data-length]')); + M.CharacterCounter.init(document.querySelectorAll('input[data-length], textarea[data-length]')); M.Dropdown.init( document.querySelectorAll('#nav-more-dropdown-trigger'), {alignment: 'right', constrainWidth: false, coverTrigger: false} diff --git a/app/templates/corpora/corpora.html.j2 b/app/templates/corpora/corpora.html.j2 index 8cc701f819d3c60ac7f1132817d9b7ae3b20663c..9be377f32a9fc7b1444f376b89d57a72242629dc 100644 --- a/app/templates/corpora/corpora.html.j2 +++ b/app/templates/corpora/corpora.html.j2 @@ -1,30 +1,46 @@ {% extends "base.html.j2" %} +{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %} + {% block page_content %} -<div class="parallax-container"> - <div class="parallax"><img src="{{ url_for('static', filename='images/parallax_hq/canvas.png') }}"></div> - <div style="position: absolute; bottom: 0; width: 100%;"> - <div class="container"> - <div class="white-text"> - <h1 id="title"><i class="nopaque-icons" style="font-size: inherit;">I</i>Corpora</h1> - </div> - <div class="white" style="padding: 0 15px; border-radius: 20px;"> - <div class="input-field"> - <i class="material-icons prefix">search</i> - <input id="public-corpora-search" placeholder="Find public corpora" type="text"> +<div class="corpus-list no-autoinit" id="corpus-list"> + <div class="parallax-container"> + <div class="parallax"><img src="{{ url_for('static', filename='images/parallax_hq/canvas.png') }}"></div> + <div style="position: absolute; bottom: 0; width: 100%;"> + <div class="container"> + <div class="white-text"> + <h1 id="title"><i class="nopaque-icons" style="font-size: inherit;">I</i>Corpora</h1> + </div> + <div class="white" style="padding: 1px 35px 0 10px; border-radius: 35px;"> + <div class="input-field"> + <i class="material-icons prefix">search</i> + <input class="search" id="corpus-list-search" type="text"> + <label for="corpus-list-search">Search corpus</label> + </div> </div> </div> </div> </div> -</div> -<div class="container"> <div class="row"> <div class="col s12" id="corpora"> <div class="card"> <div class="card-content"> - <div class="corpus-list"></div> + <div> + <table> + <thead> + <tr> + <th></th> + <th>Title and Description</th> + <th>Status</th> + <th></th> + </tr> + </thead> + <tbody class="list"></tbody> + </table> + <ul class="pagination"></ul> + </div> </div> </div> </div> @@ -36,9 +52,21 @@ {% block scripts %} {{ super() }} <script> - let publicCorporaSearchElement = document.querySelector('#public-corpora-search'); - let corpusList = CorpusList.getInstance(document.querySelector('#corpora .corpus-list .list')); - publicCorporaSearchElement.addEventListener('keyup', function() {corpusList.listjs.search(this.value);}); + let corpusListElement = document.querySelector('#corpus-list'); + let corpusListOptions = { + initialHtmlGenerator: null, + item: ` + <tr class="clickable hoverable"> + <td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td> + <td><b class="title"></b><br><i class="description"></i></td> + <td><span class="status badge new corpus-status-color corpus-status-text" data-badge-caption=""></span></td> + <td class="right-align"> + <a class="action-button btn-floating service-color darken waves-effect waves-light" data-action="view" data-service="corpus-analysis"><i class="material-icons">send</i></a> + </td> + </tr> + `.trim(), + }; + let corpusList = new CorpusList(corpusListElement, corpusListOptions); corpusList._init({{ corpora|tojson }}); </script> {% endblock scripts %} diff --git a/app/templates/main/news.html.j2 b/app/templates/main/news.html.j2 index b7204f0746a7ac23288cd0d0d25be611141aafe1..c374237c47074462047d77009236a1d5289ce144 100644 --- a/app/templates/main/news.html.j2 +++ b/app/templates/main/news.html.j2 @@ -9,7 +9,7 @@ </div> <div class="col s12"> - <div id="mastodon"></div> + <div id="aggregated-news"></div> <div class="card" id="april-2022-update"> <div class="card-content"> @@ -132,29 +132,116 @@ {% block scripts %} {{ super() }} <script> - let mastodonElement = document.querySelector('#mastodon'); - fetch(`https://fedihum.org/api/v1/accounts/109386364241901080/statuses`, {method: 'GET', headers: {Accept: 'application/json'}}) - .then((response) => {return response.json();}) - .then((statuses) => { - for (let status of statuses) { - console.log(status); - let contentHtml = `<div>${status.content}</div>` - let tagsHtml = '<p>'; - for (let tag of status.tags) { - tagsHtml += `<a href="${tag.url}" class="chip">${tag.name}</a>`; - } - tagsHtml += '</p>'; - let statusHtml = ` - <div id="${status.id}" class="card"> + function getMastodonStatuses() { + return new Promise((resolve, reject) => { + fetch(`https://fedihum.org/api/v1/accounts/109386364241901080/statuses`, {method: 'GET', headers: {Accept: 'application/json'}}) + .then((response) => { + if (!response.ok) {reject(response);} + return response.json(); + }) + .then((statuses) => {resolve(statuses);}) + }); + } + function getBisBlogsEntries() { + return new Promise((resolve, reject) => { + fetch(`https://blogs.uni-bielefeld.de/blog/uniintern/feed/entries/atom?cat=%2FAllgemein`, {method: 'GET', headers: {Accept: 'application/xml'}}) + .then((response) => { + if (!response.ok) {reject(response);} + return response.text(); + }) + .then((responseText) => {return new DOMParser().parseFromString(responseText, 'application/xml');}) + .then((xmlDocument) => {return xmlDocument.toObject();}) + .then((feed) => {resolve(feed);}); + }); + } + function sortAggregatedNews(a, b) { + let aDate; + let bDate; + + switch (a.source) { + case 'mastodon': + aDate = new Date(a.created_at); + break; + case 'big-blogs': + aDate = new Date(a.published); + break; + default: + throw new Error('Unknown source'); + } + switch (b.source) { + case 'mastodon': + bDate = new Date(b.created_at); + break; + case 'big-blogs': + bDate = new Date(b.published); + break; + default: + throw new Error('Unknown source'); + } + return bDate - aDate; + } + function aggregateNews() { + return new Promise((resolve, reject) => { + Promise.all([getMastodonStatuses(), getBisBlogsEntries()]) + .then( + (responses) => { + console.log(responses[1]); + let mastodonStatuses = responses[0].map((obj) => {return { ...obj, source: 'mastodon'}}); + let bisBlogsEntries = responses[1].feed.entry.map((obj) => {return { ...obj, source: 'big-blogs'};}); + let aggregatedNews = [...mastodonStatuses, ...bisBlogsEntries]; + aggregatedNews.sort(sortAggregatedNews); + resolve(aggregatedNews); + }, + (error) => {reject(error);} + ); + }); + } + + function mastodonStatusToHtml(status) { + return htmlString = ` + <div class="card white-text" style="background-color:#5D50E7;"> + <div class="card-content"> + <span class="card-title">New Actitvity on Mastodon</span> + ${status.content} + </div> + </div> + `.trim(); + } + function bisBlogsEntryToHtml(entry) { + return ` + <div class="row"> + <div class="col s1"> + <img src="https://blogs.uni-bielefeld.de/blog/uniintern/resource/themabilder/unilogo-square.svg" alt="Bielefeld University Blogs" class="responsive-img"> + </div> + <div class="col s11"> + <div class="card" style="background-color: #14f5b4;"> <div class="card-content"> - <span class="card-title">Mastodon News</span> - ${contentHtml} - ${tagsHtml} + <span class="card-title">${entry.title['#text']}</span> + ${entry.content['#text']} </div> </div> - `; - mastodonElement.insertAdjacentHTML('beforeend', statusHtml); + </div> + </div> + `.trim(); + } + + let aggregatedNewsElement = document.querySelector('#aggregated-news'); + aggregateNews().then((aggregatedNews) => { + for (let item of aggregatedNews) { + let itemHtmlString; + switch (item.source) { + case 'mastodon': + console.log(item); + itemHtmlString = mastodonStatusToHtml(item); + break; + case 'big-blogs': + itemHtmlString = bisBlogsEntryToHtml(item); + break; + default: + throw new Error('Unknown source'); } - }); + aggregatedNewsElement.insertAdjacentHTML('beforeend', itemHtmlString); + } + }); </script> {% endblock scripts %}