diff --git a/.eslintrc.js b/.eslintrc.js index 7bb3185..48c2cc5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,21 +1,29 @@ module.exports = { - "env": { - "browser": true, - "commonjs": true, - "node": true - }, "parserOptions": { "sourceType": "module", - "ecmaVersion": 6 + "ecmaVersion": 6, + "ecmaFeatures": { + "jsxnec": true, + }, }, "extends": "eslint:recommended", - "globals": { - "Promise": true - }, + "globals": [ + // browser + "window", "document", "URL", "XMLHttpRequest", + // browser / nodejs + "console", "setTimeout", "setInterval", "clearInterval", + // nodejs + "global", + // EcmaScript 6 + "Promise", "Map", "Set", "WeakMap", + // commonjs + "require", "module", "exports" + // + ].reduce((m, key)=>{m[key]=true;return m}, {}), "rules": { // 0 - off, 1 - warning, 2 - error "indent": ["error", 2, { "SwitchCase": 1 }], - "semi": [0, "never"], + "semi": [0, "always"], "comma-dangle": [2, "only-multiline"], "no-cond-assign": 2, "no-console": [2, { allow: ["warn", "info", "error", "assert"] }], @@ -40,6 +48,7 @@ module.exports = { // turned of as we want to be able to use this.hasOwnProperty() for instance "no-prototype-builtins": 0, "no-regex-spaces": 2, + "no-restricted-globals": [2, "Document", "Node"], "no-sparse-arrays": 0, "no-unexpected-multiline": 2, "no-unreachable": 2, @@ -47,7 +56,7 @@ module.exports = { "use-isnan": 2, "valid-jsdoc": 0, "valid-typeof": 2, - "strict": 0, // [2, "safe"], + "strict": [2, "safe"], // Best practices "accessor-pairs": 0, @@ -126,6 +135,6 @@ module.exports = { "no-undef-init": 2, "no-undefined": 0, "no-unused-vars": 2, - "no-use-before-define": [2, { "functions": false }] + "no-use-before-define": [2, { "functions": false, "classes": false }] } }; \ No newline at end of file diff --git a/.gitignore b/.gitignore index b172832..8b587d6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ config/production.json config/test.json config/dev.json ecosystem.json +sync.js # Conversion data ost-data @@ -45,4 +46,4 @@ ost-data dist dist/doc uploads -tmp \ No newline at end of file +tmp diff --git a/client/publisher/ResourceClient.js b/client/publisher/ResourceClient.js index 596054f..6ebc6de 100644 --- a/client/publisher/ResourceClient.js +++ b/client/publisher/ResourceClient.js @@ -82,27 +82,41 @@ class ResourceClient { listEntities(filters, options, cb) { let filtersRequest = encodeURIComponent(JSON.stringify(filters)) let optionsRequest = encodeURIComponent(JSON.stringify(options)) - request('GET', '/api/entities?filters=' + filtersRequest + '&options=' + optionsRequest, null, cb) + this.request('GET', '/api/entities?filters=' + filtersRequest + '&options=' + optionsRequest, null, cb) } searchEntities(query, language, filters, options, cb) { let filtersRequest = encodeURIComponent(JSON.stringify(filters)) let optionsRequest = encodeURIComponent(JSON.stringify(options)) - request('GET', '/api/entities/search?query=' + query + '&language=' + language + '&filters=' + filtersRequest + '&options=' + optionsRequest, null, cb) + this.request('GET', '/api/entities/search?query=' + query + '&language=' + language + '&filters=' + filtersRequest + '&options=' + optionsRequest, null, cb) } /* - Read all document resources + Fetch all document resources + */ + getDocumentCollaborators(documentId, cb) { + this.request('GET', '/api/collaborators/document/' + documentId, null, cb) + } + + /* + Fetch all document resources */ getDocumentResources(documentId, cb) { - request('GET', '/api/entities/document/' + documentId, null, cb) + this.request('GET', '/api/entities/document/' + documentId, null, cb) } /* Get subjects data */ getSubjects(cb) { - request('GET', '/api/entities/tree/subject', null, cb) + this.request('GET', '/api/entities/tree/subject', null, cb) + } + + /* + Get collaborator data + */ + getCollaborator(userId, cb) { + this.request('GET', '/api/collaborators/' + userId, null, cb) } } diff --git a/client/publisher/app.css b/client/publisher/app.css index c0cf8e3..b8c5d33 100644 --- a/client/publisher/app.css +++ b/client/publisher/app.css @@ -1,5 +1,4 @@ /* Publisher Component styles */ -@import '../../node_modules/substance/dist/substance.next.css'; @import '../../node_modules/leaflet/dist/leaflet.css'; @import '../../node_modules/leaflet-control-geocoder/dist/Control.Geocoder.css'; @import './publisher.css'; diff --git a/client/publisher/app.js b/client/publisher/app.js index ac92a5a..a79e40e 100644 --- a/client/publisher/app.js +++ b/client/publisher/app.js @@ -1,5 +1,5 @@ import { substanceGlobals } from 'substance' -import { Archivist, ArchivistConfigurator } from 'archivist' +import { Archivist, ArchivistConfigurator } from 'archivist-js' import Package from './package' substanceGlobals.DEBUG_RENDERING = true; diff --git a/client/publisher/index.html b/client/publisher/index.html index 63333e0..9d10f4e 100644 --- a/client/publisher/index.html +++ b/client/publisher/index.html @@ -2,22 +2,18 @@ Archivist Publisher - - + - \ No newline at end of file + diff --git a/client/publisher/index.production.html b/client/publisher/index.production.html index db881fb..7b96087 100644 --- a/client/publisher/index.production.html +++ b/client/publisher/index.production.html @@ -2,22 +2,18 @@ OST Publisher - - + - \ No newline at end of file + diff --git a/client/publisher/package.js b/client/publisher/package.js index c2d919b..e36e9cd 100644 --- a/client/publisher/package.js +++ b/client/publisher/package.js @@ -1,7 +1,8 @@ -import { ProseArticle } from 'substance' -import { ArchivistPackage, ArchivistSubConfigurator, CommentsPackage, DocumentsPackage, IndentationPackage, MetadataEditorPackage, ResourcesPackage, TimecodeAnnotatorPackage, UsersPackage, WhitespacePackage } from 'archivist' +import { ProseEditorPackage } from 'substance' +import { ArchivistPackage, ArchivistSubConfigurator, CommentsPackage, IndentationPackage, MetadataEditorPackage, ResourcesPackage, TimecodeAnnotatorPackage, UsersPackage, WhitespacePackage } from 'archivist-js' import InterviewPackage from '../../packages/interview/package' import FormsPackage from '../../packages/forms/package' +import DocumentsPackage from '../../packages/documents/package' import DefinitionManagerPackage from '../../packages/definition-manager/package' import PersonManagerPackage from '../../packages/person-manager/package' import PrisonManagerPackage from '../../packages/prison-manager/package' @@ -22,6 +23,8 @@ import Subject from '../../packages/subjects/Subject' import Subjects from '../../packages/subjects/package' import Toponym from '../../packages/toponym/Toponym' +const { ProseArticle } = ProseEditorPackage + let appConfig = 'ARCHIVISTCONFIG' appConfig = JSON.parse(appConfig) @@ -29,9 +32,10 @@ export default { name: 'archivist-publisher', configure: function(config) { // Use the default Archivist package + config.setDefaultLanguage(appConfig.defaultLanguage) config.import(ArchivistPackage) config.import(DocumentsPackage) - // Override Archivist form package + // Override Archivist form package config.import(FormsPackage) // Manage person entity type config.import(PersonManagerPackage) @@ -72,7 +76,8 @@ export default { let EntitiesConfigurator = new ArchivistSubConfigurator() EntitiesConfigurator.defineSchema({ name: 'archivist-entities', - ArticleClass: ProseArticle + version: '1.0.0', + DocumentClass: ProseArticle }) EntitiesConfigurator.addNode(Definition) EntitiesConfigurator.addNode(Person) @@ -107,15 +112,15 @@ export default { config.setResourceClient(ResourceClient) config.setMenuItems([ - {icon: 'fa-file-text', label: 'Documents', action: 'archive'}, - {icon: 'fa-tags', label: 'Subjects', action: 'subjects'}, - {icon: 'fa-users', label: 'Persons', action: 'persons'}, - {icon: 'fa-th', label: 'Prisons', action: 'prisons'}, - {icon: 'fa-globe', label: 'Toponyms', action: 'toponyms'}, - {icon: 'fa-book', label: 'Definitions', action: 'definitions'}, - {icon: 'fa-id-badge', label: 'Users', action: 'users'} + {icon: 'fa-file-text', label: 'documents', action: 'archive'}, + {icon: 'fa-tags', label: 'subjects', action: 'subjects'}, + {icon: 'fa-users', label: 'persons', action: 'persons'}, + {icon: 'fa-th', label: 'prisons', action: 'prisons'}, + {icon: 'fa-globe', label: 'toponyms', action: 'toponyms'}, + {icon: 'fa-book', label: 'definitions', action: 'definitions'}, + {icon: 'fa-id-badge', label: 'users', action: 'users'} ]) config.setDefaultResourceTypes(['definition', 'person', 'prison', 'toponym']) } -} \ No newline at end of file +} diff --git a/client/publisher/publisher.css b/client/publisher/publisher.css index cb3ca9a..dc45a5e 100644 --- a/client/publisher/publisher.css +++ b/client/publisher/publisher.css @@ -1,9 +1,11 @@ @import '../../packages/forms/_index.css'; @import '../../packages/header/_index.css'; @import '../../packages/definition/_index.css'; +@import '../../packages/ost-publisher/_index.css'; +@import '../../packages/documents/_index.css'; @import '../../packages/person/_index.css'; @import '../../packages/prison/_index.css'; @import '../../packages/subject/_index.css'; @import '../../packages/subject-manager/_index.css'; @import '../../packages/subjects-editor-context/_index.css'; -@import '../../packages/toponym/_index.css'; \ No newline at end of file +@import '../../packages/toponym/_index.css'; diff --git a/client/scholar/app.css b/client/scholar/app.css index fe28e96..94092b4 100644 --- a/client/scholar/app.css +++ b/client/scholar/app.css @@ -1,5 +1,4 @@ /* Scholar styles */ -@import '../../node_modules/substance/dist/substance.next.css'; @import '../../node_modules/plyr/dist/plyr.css'; @import '../../node_modules/leaflet/dist/leaflet.css'; @import '../../node_modules/leaflet.markercluster/dist/MarkerCluster.css'; diff --git a/client/scholar/assets/multirange.js b/client/scholar/assets/multirange.js deleted file mode 100644 index cbac586..0000000 --- a/client/scholar/assets/multirange.js +++ /dev/null @@ -1,94 +0,0 @@ -(function() { - "use strict"; - - var supportsMultiple = self.HTMLInputElement && "valueLow" in HTMLInputElement.prototype; - - var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"); - - self.multirange = function(input) { - if (supportsMultiple || input.classList.contains("multirange")) { - return; - } - - var values = input.getAttribute("value").split(","); - var min = +input.min || 0; - var max = +input.max || 100; - var ghost = input.cloneNode(); - ghost.onchange = function() { - var event = document.createEvent('Event'); - event.initEvent('change', true, true); - input.addEventListener('change', function (e) { - }, false); - input.dispatchEvent(event); - } - ghost.oninput = function() { - var event = document.createEvent('Event'); - event.initEvent('input', true, true); - input.addEventListener('input', function (e) { - }, false); - input.dispatchEvent(event); - } - - input.classList.add("multirange", "original"); - ghost.classList.add("multirange", "ghost"); - - input.value = values[0] || min + (max - min) / 2; - ghost.value = values[1] || min + (max - min) / 2; - - input.parentNode.insertBefore(ghost, input.nextSibling); - - Object.defineProperty(input, "originalValue", descriptor.get ? descriptor : { - // Fuck you Safari >:( - get: function() { return this.value; }, - set: function(v) { this.value = v; } - }); - - Object.defineProperties(input, { - valueLow: { - get: function() { return Math.min(this.originalValue, ghost.value); }, - set: function(v) { this.originalValue = v; }, - enumerable: true - }, - valueHigh: { - get: function() { return Math.max(this.originalValue, ghost.value); }, - set: function(v) { ghost.value = v; }, - enumerable: true - } - }); - - if (descriptor.get) { - // Again, fuck you Safari - Object.defineProperty(input, "value", { - get: function() { return this.valueLow + "," + this.valueHigh; }, - set: function(v) { - var values = v.split(","); - this.valueLow = values[0]; - this.valueHigh = values[1]; - }, - enumerable: true - }); - } - - function update() { - ghost.style.setProperty("--low", 100 * ((input.valueLow - min) / (max - min)) + 1 + "%"); - ghost.style.setProperty("--high", 100 * ((input.valueHigh - min) / (max - min)) - 1 + "%"); - } - - input.addEventListener("input", update); - ghost.addEventListener("input", update); - - update(); - } - - multirange.init = function() { - Array.from(document.querySelectorAll("input[type=range][multiple]:not(.multirange)")).forEach(multirange); - } - - if (document.readyState == "loading") { - document.addEventListener("DOMContentLoaded", multirange.init); - } - else { - multirange.init(); - } - -})(); \ No newline at end of file diff --git a/client/scholar/assets/polyfills.js b/client/scholar/assets/polyfills.js new file mode 100644 index 0000000..f49f8f6 --- /dev/null +++ b/client/scholar/assets/polyfills.js @@ -0,0 +1 @@ +Array.from||(Array.from=function(e){"use strict";return[].slice.call(e)}),function e(t,n,r){function i(o,l){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!l&&a)return a(o,!0);if(u)return u(o,!0);var s=new Error("Cannot find module '"+o+"'");throw s.code="MODULE_NOT_FOUND",s}var c=n[o]={exports:{}};t[o][0].call(c.exports,function(e){var n=t[o][1][e];return i(n||e)},c,c.exports,e,t,n,r)}return n[o].exports}for(var u="function"==typeof require&&require,o=0;o Archivist Scholar - - + + + - - + diff --git a/client/scholar/index.production.html b/client/scholar/index.production.html index ba3de95..eb6def9 100644 --- a/client/scholar/index.production.html +++ b/client/scholar/index.production.html @@ -17,25 +17,26 @@ - - - - - - - + + + + + + \ No newline at end of file diff --git a/client/scholar/package.js b/client/scholar/package.js index fdb0359..7a7d9a3 100644 --- a/client/scholar/package.js +++ b/client/scholar/package.js @@ -1,4 +1,4 @@ -import { ProseArticle } from 'substance' +import { ProseEditorPackage } from 'substance' import ScholarPackage from '../../packages/scholar/package' import HeaderPackage from '../../packages/header/package' import ExplorerPackage from '../../packages/explorer/package' @@ -21,6 +21,8 @@ import Person from '../../packages/person/Person' import Prison from '../../packages/prison/Prison' import Toponym from '../../packages/toponym/Toponym' +const { ProseArticle } = ProseEditorPackage + let appConfig = 'ARCHIVISTCONFIG' appConfig = JSON.parse(appConfig) @@ -57,7 +59,7 @@ export default { let EntitiesConfigurator = new ScholarSubConfigurator() EntitiesConfigurator.defineSchema({ name: 'archivist-entities', - ArticleClass: ProseArticle + DocumentClass: ProseArticle }) EntitiesConfigurator.addNode(Definition) EntitiesConfigurator.addNode(Person) diff --git a/config/default.json b/config/default.json index c7e4417..b6fa020 100644 --- a/config/default.json +++ b/config/default.json @@ -20,4 +20,4 @@ "sender": "archivist@archivist" }, "db_url": "postgres://archivist_user:archivist_pw@localhost:5432/archivist" -} \ No newline at end of file +} diff --git a/db/reset.sql b/db/reset.sql index 4cae5c2..24b0166 100644 --- a/db/reset.sql +++ b/db/reset.sql @@ -54,14 +54,15 @@ CREATE INDEX entities_created_idx ON entities(created); CREATE INDEX tsv_entities_idx ON entities USING gin(tsv); -- Entity name first letter index CREATE INDEX entity_name_first_letter ON entities USING btree (lower(cast(name AS varchar(1)))); +CREATE INDEX entity_names ON entities USING btree (name, "entityId") -CREATE OR REPLACE FUNCTION arr2text(text[]) +CREATE OR REPLACE FUNCTION arr2text(text[]) RETURNS text LANGUAGE sql IMMUTABLE AS 'SELECT $1::text'; CREATE FUNCTION entities_search_trigger() RETURNS trigger AS $$ begin new.tsv := - setweight(to_tsvector('russian', COALESCE(new.name,'')), 'A') || + setweight(to_tsvector('russian', COALESCE(new.name,'')), 'A') || setweight(to_tsvector('russian', COALESCE(arr2text(new.synonyms),'')),'B') || setweight(to_tsvector('russian', COALESCE(new.description,'')),'C'); return new; @@ -83,6 +84,7 @@ CREATE TABLE "documents" ( title text, language text, annotations text[], + collaborators text[], "references" jsonb, "fullText" text, "updatedAt" timestamp, @@ -101,7 +103,7 @@ CREATE INDEX tsv_documents_idx ON documents USING gin(tsv); CREATE FUNCTION documents_search_trigger() RETURNS trigger AS $$ begin new.tsv := - setweight(to_tsvector('russian', COALESCE(new.title,'')), 'A') || + setweight(to_tsvector('russian', COALESCE(new.title,'')), 'A') || setweight(to_tsvector('russian', COALESCE(new.meta->>'summary','')),'B') || setweight(to_tsvector('russian', COALESCE(new."fullText",'')),'C'); return new; @@ -147,7 +149,7 @@ CREATE TABLE "fragments" ( "time" text, -- array with annotations references annotations text[], - -- annotations references as key/value object + -- annotations references as key/value object -- (key is reference and value is number of references) "references" jsonb, -- previous fragment reference @@ -170,4 +172,4 @@ end $$ LANGUAGE plpgsql; CREATE TRIGGER tsvectorfragmentsupdate BEFORE INSERT OR UPDATE -ON fragments FOR EACH ROW EXECUTE PROCEDURE fragments_search_trigger(); \ No newline at end of file +ON fragments FOR EACH ROW EXECUTE PROCEDURE fragments_search_trigger(); diff --git a/fixGeo.js b/fixGeo.js new file mode 100644 index 0000000..12b0c48 --- /dev/null +++ b/fixGeo.js @@ -0,0 +1,618 @@ +/* + General script for importing data from old MongoDB setup. + Data must be exported as JSON before running this script. + Script expects path to collection's JSON files on input, + e.g. import documents=../../data/documents.json users=../../data/users.json etc +*/ + +let process = require('process') +let fs = require('fs') +let Promise = require('bluebird') +let forEach = require('lodash/forEach') +let map = require('lodash/map') +let uniq = require('lodash/uniq') +let JSONConverter = require('substance').JSONConverter +let documentHelpers = require('substance').documentHelpers +let Database = require('./packages/server/Database') +let Configurator = require('archivist-js').ServerConfigurator +let EnginePackage = require('./packages/engine/package') +let IndexerPackage = require('./packages/indexer/package') +let InterviewPackage = require('./dist/ost.cjs').InterviewPackage + +let args = process.argv.slice(2) +let config = {} + +args.forEach(function(arg) { + let option = arg.split('=') + if(option.length === 2) { + config[option[0]] = option[1] + } +}) + +let db = new Database() +let configurator = new Configurator().import(InterviewPackage) +let converter = new JSONConverter() +configurator.setDBConnection(db) +configurator.import(EnginePackage) +configurator.import(IndexerPackage) + +let defaultUser = '54bd3cff742c750408dacf9d' +let entitiesTypesMap = {} +let subjectsTree = {} + +function importUsers() { + let exists = _fileExists(config.users) + if(!exists) return + + let jsonContents = fs.readFileSync(config.users) + let jsonData = JSON.parse(jsonContents) + let usersData = [] + + jsonData.forEach(function(user) { + let userData = { + userId: user._id['$oid'], + name: user.name, + email: user.email, + created: _getTimeFromId(user._id['$oid']) + } + usersData.push(userData) + }) + + let userStore = configurator.getStore('user') + return userStore.seed(usersData) +} + +function importPersons() { + let exists = _fileExists(config.persons) + if(!exists) return + + let jsonContents = fs.readFileSync(config.persons) + let jsonData = JSON.parse(jsonContents) + let personsData = [] + + jsonData.forEach(function(person) { + let personData = { + entityId: person._id['$oid'], + name: person.name, + description: person.description, + synonyms: [], + created: person.createdAt['$date'], + edited: person.updatedAt['$date'], + entityType: 'person', + data: { + name: person.name, + synonyms: [], + description: person.description, + global: person.global, + } + } + personData.updatedBy = person.edited ? person.edited['$oid'] : defaultUser + personData.userId = person.edited ? person.edited['$oid'] : defaultUser + entitiesTypesMap[personData.entityId] = personData.entityType + personsData.push(personData) + }) + + let entityStore = configurator.getStore('entity') + return entityStore.seed(personsData) +} + +function importDefinitions() { + let exists = _fileExists(config.definitions) + if(!exists) return + + let jsonContents = fs.readFileSync(config.definitions) + let jsonData = JSON.parse(jsonContents) + let definitionsData = [] + + jsonData.forEach(function(definition) { + let definitionData = { + entityId: definition._id['$oid'], + name: definition.title, + description: definition.description, + synonyms: [], + created: definition.createdAt['$date'], + edited: definition.updatedAt['$date'], + entityType: 'definition', + data: { + name: definition.title, + description: definition.description, + definitionType: definition.definition_type + } + } + + definitionData.synonyms = definition.synonyms + if(definitionData.synonyms.indexOf(definition.title) > -1) { + let pos = definitionData.synonyms.indexOf(definition.title) + definitionData.synonyms.splice(pos, 1) + } + definitionData.data.synonyms = definitionData.synonyms + + definitionData.updatedBy = definition.edited ? definition.edited['$oid'] : defaultUser + definitionData.userId = definition.edited ? definition.edited['$oid'] : defaultUser + entitiesTypesMap[definitionData.entityId] = definitionData.entityType + definitionsData.push(definitionData) + }) + + let entityStore = configurator.getStore('entity') + return entityStore.seed(definitionsData) +} + +function fixLocations() { + let entityStore = configurator.getStore('entity') + + return new Promise(function(resolve) { + db.connection.run("SELECT * from entities WHERE data->>'country' = 'Россия' AND (data->'point'->>0)::float < 42 AND (data->'point'->>1)::float > 30", function(err, res) { + if (err) { + console.error('Entity read error', err) + } + + resolve(res) + }) + }).then(res => { + let fixes = [] + res.forEach(r=>{ + r.data.point = r.data.point.reverse() + fixes.push( + entityStore.updateEntity(r.entityId,r) + ) + }) + return Promise.all(fixes) + }) +} + +function importLocations() { + let exists = _fileExists(config.locations) + if(!exists) return + + let jsonContents = fs.readFileSync(config.locations) + let jsonData = JSON.parse(jsonContents) + let locationsData = [] + + jsonData.forEach(function(location) { + let locationData = { + entityId: location._id['$oid'], + name: location.name, + description: location.description, + edited: location.updatedAt['$date'], + entityType: location.type, + data: { + name: location.name, + description: location.description, + country: location.country, + point: location.point + } + } + + locationData.synonyms = location.synonyms + if(locationData.synonyms.indexOf(location.name) > -1) { + let pos = locationData.synonyms.indexOf(location.name) + locationData.synonyms.splice(pos, 1) + } + locationData.data.synonyms = locationData.synonyms + + if(locationData.entityType === 'toponym') { + locationData.data.currentName = location.current_name + } else if (locationData.entityType === 'prison') { + locationData.data.nearestLocality = location.nearest_locality + locationData.data.prisonType = location.prison_type + } + + locationData.created = location.createdAt ? location.createdAt['$date'] : location.updatedAt['$date'] + locationData.updatedBy = location.edited ? location.edited['$oid'] : defaultUser + locationData.userId = location.edited ? location.edited['$oid'] : defaultUser + entitiesTypesMap[locationData.entityId] = locationData.entityType + locationsData.push(locationData) + }) + + let entityStore = configurator.getStore('entity') + return entityStore.seed(locationsData) +} + +function importSubjects() { + let exists = _fileExists(config.subjects) + if(!exists) return + + let jsonContents = fs.readFileSync(config.subjects) + let jsonData = JSON.parse(jsonContents) + let subjectsData = [] + + jsonData.forEach(function(subject) { + let subjectData = { + entityId: subject._id['$oid'], + name: subject.name, + synonyms: [subject.name], + description: subject.description || '', + edited: subject.updatedAt['$date'], + entityType: 'subject', + data: { + name: subject.name, + workname: subject.workname, + description: subject.description || '', + parent: subject.parent || null, + position: subject.position + } + } + + if(subject.name !== subject.workname) subjectData.synonyms.push(subject.workname) + subjectData.created = subject.createdAt ? subject.createdAt['$date'] : subject.updatedAt['$date'] + subjectData.updatedBy = subject.edited ? subject.edited['$oid'] : defaultUser + subjectData.userId = subject.edited ? subject.edited['$oid'] : defaultUser + subjectsData.push(subjectData) + + subjectsTree[subjectData.entityId] = {id: subjectData.entityId, parent: subjectData.data.parent} + }) + + let entityStore = configurator.getStore('entity') + return entityStore.seed(subjectsData) +} + +function importDocuments() { + let exists = _fileExists(config.documents) + if(!exists) return + + let jsonContents = fs.readFileSync(config.documents) + let jsonData = JSON.parse(jsonContents) + + let changes = {} + let documents = {} + let snapshots = {} + + jsonData.forEach(function(doc) { + // Document processing + // =================== + + let docId = doc._id['$oid'] + + let documentData = { + nodes: [], + schema: { + name: "archivist-interview", + version: "1.0.0" + } + } + + let bodyNode = { + id: 'body', + type: 'container', + nodes: [] + } + + let contentNodes = doc.nodes.content.nodes + let entities = [] + let subjects = [] + let references = {} + let paragraphsMap = {} + let fullText = '' + let metaSource = {} + let metaNode = {} + let pIndex = 1 + let sIndex = 1 + let eIndex = 1 + let tIndex = 1 + let cIndex = 1 + let subjectIndex = 1 + let entityIndexes = { + 'person': 1, + 'definition': 1, + 'prison': 1, + 'toponym': 1 + } + + contentNodes.forEach(function(nodeId) { + let paragraphId = 'paragraph-' + pIndex + let paragraph = doc.nodes[nodeId] + paragraph.id = paragraphId + + fullText += '\r\n' + paragraph.content + paragraphsMap[nodeId] = paragraphId + bodyNode.nodes.push(paragraphId) + documentData.nodes.push(paragraph) + pIndex++ + }) + + forEach(doc.nodes, function(node) { + if(node.startOffset < 0 || node.endOffset < 0) { + console.log(doc._id['$oid'], ' must be fixed') + return + } + if(node.type === 'strong') { + node.id = 'strong-' + sIndex + node.path[0] = paragraphsMap[node.path[0]] + node.start = { + offset: node.startOffset, + path: node.path + } + node.end = { + offset: node.endOffset, + path: node.path + } + documentData.nodes.push(node) + sIndex++ + } else if (node.type === 'emphasis') { + node.id = 'emphasis-' + eIndex + node.path[0] = paragraphsMap[node.path[0]] + node.start = { + offset: node.startOffset, + path: node.path + } + node.end = { + offset: node.endOffset, + path: node.path + } + documentData.nodes.push(node) + eIndex++ + } else if (node.type === 'timecode') { + node.id = 'timecode-' + tIndex + node.path[0] = paragraphsMap[node.path[0]] + node.start = { + offset: node.startOffset, + path: node.path + } + node.end = { + offset: node.endOffset, + path: node.path + } + documentData.nodes.push(node) + tIndex++ + } else if (node.type === 'subject_reference') { + let subject = { + id: 'subject-' + subjectIndex, + containerId: 'body', + type: 'subject', + start: { + path: [ + paragraphsMap[node.startPath[0]], + 'content' + ], + offset: node.startOffset + }, + end: { + path: [ + paragraphsMap[node.endPath[0]], + 'content' + ], + offset: node.endOffset + }, + reference: node.target + } + forEach(node.target, function(subject) { + if(references[subject]) { + references[subject]++ + } else { + references[subject] = 1 + } + }) + subjects = subjects.concat(node.target) + documentData.nodes.push(subject) + subjectIndex++ + } else if (node.type === 'comment') { + let comment = { + id: 'comment-' + cIndex, + content: node.content, + containerId: 'body', + type: 'comment', + start: { + path: [ + paragraphsMap[node.startPath[0]], + 'content' + ], + offset: node.startOffset + }, + end: { + path: [ + paragraphsMap[node.endPath[0]], + 'content' + ], + offset: node.endOffset + }, + author: defaultUser, + createdAt: node.created_at, + replies: [] + } + documentData.nodes.push(comment) + cIndex++ + } else if (node.type === 'entity_reference') { + let entityType = entitiesTypesMap[node.target] + let entity = { + id: entityType + '-' + entityIndexes[entityType], + end: { + offset: node.endOffset, + path: [ + paragraphsMap[node.path[0]], + 'content' + ] + }, + start: { + offset: node.startOffset, + path: [ + paragraphsMap[node.path[0]], + 'content' + ] + }, + reference: node.target, + type: entityType + } + if(references[node.target]) { + references[node.target]++ + } else { + references[node.target] = 1 + } + entities.push(node.target) + documentData.nodes.push(entity) + entityIndexes[entityType]++ + } else if (node.type === 'waypoint') { + node.density = node.density.toString() + documentData.nodes.push(node) + } else if (node.type === 'document') { + metaSource = node + metaNode = { + id: 'meta', + type: 'meta', + title: node.title, + state: '', + abstract: node.abstract, + abstract_translation: node.abstract_en, + abstract_translation_second: node.abstract_de, + media_id: node.media_id, + operator: node.operator, + conductor: node.conductor, + record_type: node.record_type, + project_name: node.project_name, + project: node.project || 'Международный проект документации рабского и принудительного труда', + published_on: '2016-11-16', + short_summary: node.short_summary, + short_summary_translation: node.short_summary_en, + interview_date: '1990-11-16', + sound_operator: node.sound_operator, + interviewee_bio: node.interviewee_bio, + interviewee_bio_translation: node.interviewee_bio_en, + interviewee_bio_translation_second: node.interviewee_bio_de, + persons_present: node.persons_present, + project_location: node.project_location, + interview_duration: parseInt(node.interview_duration, 10), + interview_location: node.interview_location, + interviewee_photo: node.interviewee_photo, + interviewee_category: node.interviewee_category, + interviewee_detention_place_type: node.forced_labor_type, + interviewee_forced_labor_type: node.detention_place_type, + interviewee_state: node.person_state || 'военнопленный', + interviewee_military_service: node.military_service || false, + interviewee_sex: node.sex || 'мужчина', + interviewee_place_of_birth: node.place_of_birth, + interviewee_year_of_birth: node.year_of_birth, + interviewee_enslaving_year: node.enslaving_year, + interviewee_homecoming_year: node.homecoming_year, + interviewee_waypoints: node.interviewee_waypoints || [] + } + + try { + metaNode.published_on = new Date(node.published_on).toISOString() + } catch(e) { + console.log('Invalid published time:', node.published_on) + } + + try { + metaNode.interview_date = new Date(node.interview_date).toISOString() + } catch(e) { + console.log('Invalid interview time:', node.interview_date) + } + + if(node.transcripted) metaNode.state = 'transcripted' + if(node.verified) metaNode.state = 'verified' + if(node.finished) metaNode.state = 'finished' + if(node.published) metaNode.state = 'published' + + documentData.nodes.unshift(metaNode) + } + + }) + + documentData.nodes.unshift(bodyNode) + + entities = uniq(entities) + subjects = uniq(subjects) + + let annotations = [] + annotations = annotations.concat(entities, subjects) + + forEach(references, function(cnt, ref) { + if(subjectsTree[ref]) { + let parent = subjectsTree[ref].parent + while(parent) { + if(references[parent]) { + references[parent]++ + } else { + references[parent] = 1 + } + parent = subjectsTree[parent] ? subjectsTree[parent].parent : false + } + } + }) + + let article = configurator.createArticle(); + let jsonDoc = converter.importDocument(article, documentData); + let changeset = documentHelpers.getChangeFromDocument(jsonDoc); + + changes[docId] = [changeset] + + documents[docId] = { + documentId: docId, + schemaName: 'archivist-interview', + schemaVersion: '1.0.0', + info: {}, + meta: metaNode, + version: 1, + indexedVersion: 0, + title: metaNode.title, + language: 'russian', + annotations: annotations, + references: references, + fullText: fullText, + updatedAt: metaSource.updated_at, + updatedBy: defaultUser, + userId: defaultUser + } + + // snapshots[docId] = { + // '1': { + // documentId: docId, + // version: 1, + // data: documentData, + // created: new Date() + // } + // } + + }) + + let changeStore = configurator.getStore('change') + let documentStore = configurator.getStore('document') + let snapshotStore = configurator.getStore('snapshot') + + return documentStore.seed(documents) + .then(function() { + return changeStore.seed(changes) + }) + .then(function() { + return snapshotStore.seed(snapshots) + }) +} + +function reindexEntities() { + return new Promise(function(resolve) { + db.connection.run("REINDEX INDEX anno_refs", function(err, res) { + if (err) { + console.error('Entity reindexing error', err) + } + + resolve(res) + }) + }) +} + +function _fileExists(path) { + try { + fs.accessSync(path, fs.F_OK) + return true + } catch (e) { + console.log('Sorry, given file doesn\'t exists:', path) + return false + } +} + +function _getTimeFromId(id) { + let timestamp = id.toString().substring(0, 8) + let date = new Date(parseInt(timestamp, 16) * 1000) + return date.toISOString() +} + +fixLocations() + .then(function() { + console.log('Entities has been fixed!') + db.shutdown() + process.exit() + }) + .catch(function(error) { + console.error(error) + db.shutdown() + process.exit(1) + }) diff --git a/import.js b/import.js index 02ef99e..d68d2ec 100644 --- a/import.js +++ b/import.js @@ -14,7 +14,7 @@ let uniq = require('lodash/uniq') let JSONConverter = require('substance').JSONConverter let documentHelpers = require('substance').documentHelpers let Database = require('./packages/server/Database') -let Configurator = require('archivist').ServerConfigurator +let Configurator = require('archivist-js').ServerConfigurator let EnginePackage = require('./packages/engine/package') let IndexerPackage = require('./packages/indexer/package') let InterviewPackage = require('./dist/ost.cjs').InterviewPackage @@ -26,7 +26,7 @@ args.forEach(function(arg) { let option = arg.split('=') if(option.length === 2) { config[option[0]] = option[1] - } + } }) let db = new Database() @@ -148,7 +148,7 @@ function importLocations() { jsonData.forEach(function(location) { let locationData = { entityId: location._id['$oid'], - name: location.name, + name: location.name, description: location.description, edited: location.updatedAt['$date'], entityType: location.type, @@ -233,7 +233,7 @@ function importDocuments() { let changes = {} let documents = {} let snapshots = {} - + jsonData.forEach(function(doc) { // Document processing // =================== @@ -417,6 +417,9 @@ function importDocuments() { entities.push(node.target) documentData.nodes.push(entity) entityIndexes[entityType]++ + } else if (node.type === 'waypoint') { + node.density = node.density.toString() + documentData.nodes.push(node) } else if (node.type === 'document') { metaSource = node metaNode = { @@ -455,7 +458,8 @@ function importDocuments() { interviewee_place_of_birth: node.place_of_birth, interviewee_year_of_birth: node.year_of_birth, interviewee_enslaving_year: node.enslaving_year, - interviewee_homecoming_year: node.homecoming_year + interviewee_homecoming_year: node.homecoming_year, + interviewee_waypoints: node.interviewee_waypoints || [] } try { @@ -475,7 +479,7 @@ function importDocuments() { if(node.finished) metaNode.state = 'finished' if(node.published) metaNode.state = 'published' - documentData.nodes.unshift(metaNode) + documentData.nodes.unshift(metaNode) } }) @@ -536,11 +540,11 @@ function importDocuments() { // } }) - + let changeStore = configurator.getStore('change') let documentStore = configurator.getStore('document') let snapshotStore = configurator.getStore('snapshot') - + return documentStore.seed(documents) .then(function() { return changeStore.seed(changes) @@ -617,4 +621,4 @@ importUsers() console.error(error) db.shutdown() process.exit(1) - }) \ No newline at end of file + }) diff --git a/index.es.js b/index.es.js index 21e8a61..0acdce4 100644 --- a/index.es.js +++ b/index.es.js @@ -1,2 +1,4 @@ // interview -export { default as InterviewPackage } from './packages/interview/package' \ No newline at end of file +export { default as InterviewPackage } from './packages/interview/package' +// subjects +export { default as SubjectsPackage } from './packages/subjects/package' \ No newline at end of file diff --git a/make.js b/make.js index 24e93db..8c47856 100644 --- a/make.js +++ b/make.js @@ -1,67 +1,73 @@ -var b = require('substance-bundler'); -var fs = require('fs') -var config = require('config') +const b = require('substance-bundler') +const fs = require('fs') +const config = require('config') b.task('clean', function() { b.rm('./dist') }) - // copy assets b.task('assets', function() { - b.copy('node_modules/font-awesome', './dist/font-awesome') - b.copy('node_modules/leaflet.markercluster/dist', './dist/markercluster') + b.copy('node_modules/font-awesome', './dist/libs/font-awesome') + b.copy('node_modules/leaflet.markercluster/dist', './dist/libs/markercluster') b.copy('node_modules/leaflet/dist/images', './dist/publisher/images') b.copy('node_modules/leaflet-control-geocoder/dist/images', './dist/publisher/images') }) - -// this optional task makes it easier to work on Substance core -b.task('substance', function() { - b.make('substance', 'build') - b.copy('node_modules/substance/dist', './dist/substance') - b.minify('./dist/substance/substance.js', './dist/substance/substance.min.js') +// dev +b.task('publisher', buildApp('publisher')) +b.task('scholar', buildApp('scholar')) +// production +b.task('publisher-min', buildApp('publisher', true)) +b.task('scholar-min', buildApp('scholar', true)) +// build apps +b.task('client', ['publisher', 'scholar']) +b.task('client-min', ['scholar-min', 'publisher-min']) +// build libraries +b.task('deps', () => { + _buildDeps() }) - -b.task('archivist', function() { - b.make('archivist', 'build') - b.copy('node_modules/archivist/dist', './dist/archivist') - b.minify('./dist/archivist/archivist.js', './dist/archivist/archivist.min.js') +b.task('deps-min', () => { + _buildDeps(true) +}) +// build server js +b.task('server', () => { + buildServerJS() }) -function buildArchivistDev() { - return function() { - b.make('archivist', 'dev') - b.copy('node_modules/archivist/dist', './dist/archivist') - } -} +// build all +b.task('default', ['dev']) +b.task('dev', ['clean', 'assets', 'deps', 'server', 'client']) +b.task('production', ['clean', 'assets', 'deps', 'server', 'client']) function buildApp(app, production) { return function() { - if(production) { - b.copy('client/'+app+'/index.production.html', './dist/'+app+'/index.html') - } else { - b.copy('client/'+app+'/index.html', './dist/'+app+'/') - } - b.copy('client/'+app+'/assets', './dist/'+app+'/assets/') - b.css('./client/' + app + '/app.css', 'dist/' + app + '/' + app + '.css', {variables: true}) + b.copy('client/'+ app +'/index.production.html', './dist/'+ app +'/index.html') + //b.copy('client/'+ app +'/index.html', './dist/'+ app +'/') + b.copy('client/'+ app +'/assets', './dist/'+ app +'/assets/') + b.css('client/' + app + '/app.css', 'dist/' + app + '/' + app + '.css') + b.js('client/' + app + '/app.js', { - // need buble if we want to minify later - buble: true, - external: ['substance', 'archivist'], - commonjs: { + targets: [{ + dest: './dist/' + app + '/app.js', + format: 'umd', moduleName: 'app' + }], + commonjs: { include: [ - 'node_modules/moment/moment.js', - 'node_modules/plyr/src/js/plyr.js', + 'node_modules/moment/moment.js', + 'node_modules/plyr/src/js/plyr.js', 'node_modules/leaflet/dist/leaflet-src.js', 'node_modules/leaflet.markercluster/dist/leaflet.markercluster-src.js', 'node_modules/leaflet-control-geocoder/dist/Control.Geocoder.js' - ] + ] }, - targets: [{ - dest: './dist/' + app + '/app.js', - format: 'umd', - moduleName: app - }] + external: ['substance', 'archivist'], + globals: { + 'substance': 'substance', + 'archivist-js': 'archivist-js' + }, + buble: production === true, + useStrict: production !== true }) + b.custom('injecting config', { src: './dist/' + app + '/app.js', dest: './dist/' + app + '/' + app + '.js', @@ -69,49 +75,56 @@ function buildApp(app, production) { const code = fs.readFileSync(file[0], 'utf8') const result = code.replace(/ARCHIVISTCONFIG/g, JSON.stringify(config.get('app'))) fs.writeFileSync(this.outputs[0], result, 'utf8') - } + } }) - b.minify('./dist/' + app + '/' + app + '.js', './dist/' + app + '/' + app + '.min.js') - b.copy('./dist/' + app + '/app.js.map', './dist/' + app + '/' + app + '.js.map') + if(production) { + b.minify('./dist/' + app + '/' + app + '.js') + } else { + b.copy('./dist/' + app + '/app.js.map', './dist/' + app + '/' + app + '.js.map') + } b.rm('./dist/' + app + '/app.js') b.rm('./dist/' + app + '/app.js.map') } } -function _ostJS() { +function buildServerJS() { b.js('./index.es.js', { - buble: true, - external: ['substance', 'archivist'], + external: ['substance', 'archivist-js'], + globals: { + 'substance': 'substance', + 'archivist-js': 'archivist-js' + }, targets: [{ dest: 'dist/ost.cjs.js', - format: 'cjs', - sourceMapRoot: __dirname, - sourceMapPrefix: 'ost' + format: 'cjs' }] }) } -b.task('deps', ['substance', 'assets', 'archivist']) -b.task('ost', _ostJS()) +/* HELPERS */ -// dev -b.task('archivist-dev', buildArchivistDev()) -b.task('publisher', buildApp('publisher')) -b.task('scholar', buildApp('scholar')) -// production -b.task('publisher-min', buildApp('publisher', true)) -b.task('scholar-min', buildApp('scholar', true)) +/* HELPERS */ -b.task('client', ['publisher', 'scholar']) -b.task('client-min', ['scholar-min', 'publisher-min']) +function _buildDeps(min) { + b.copy('node_modules/substance/dist', './dist/libs/substance') + if(min) { + b.minify('./dist/libs/substance/substance.js', './dist/libs/substance/substance.min.js') + b.custom('applying modification', { + src: './dist/libs/substance/substance.es5.js', + dest: './dist/libs/substance/substance.legacy.js', + execute: function(file) { + const code = fs.readFileSync(file[0], 'utf8') + const result = code.replace(/(\(ref = this\)._initialize.apply\(ref, args\);)[\s\S]{13}/g, 'var ref;(ref = this)._initialize.apply(ref, args);') + fs.writeFileSync(this.outputs[0], result, 'utf8') + } + }) -// build all -b.task('default', ['deps', 'client', 'ost']) -b.task('dev', ['substance', 'assets', 'archivist-dev', 'client', 'ost']) -b.task('production', ['deps', 'client-min', 'ost']) + b.minify('./dist/libs/substance/substance.legacy.js') + } -// starts a server when CLI argument '-s' is set -b.setServerPort(5001) -b.serve({ - static: true, route: '/', folder: 'dist' -}); \ No newline at end of file + b.copy('node_modules/archivist-js/dist', './dist/libs/archivist') + if(min) { + b.minify('./dist/libs/archivist/archivist.js', './dist/libs/archivist/archivist.min.js') + b.minify('./dist/libs/archivist/archivist.es5.js') + } +} diff --git a/package.json b/package.json index b5451f0..e476b3d 100644 --- a/package.json +++ b/package.json @@ -3,25 +3,25 @@ "description": "", "main": "dist/ost.cjs.js", "dependencies": { - "archivist": "archivist/archivist#960984b6073e0b825ac618a2d3b4bde4ddee65ab", + "archivist-js": "2.0.0-preview.9", "bluebird": "^3.4.7", "body-parser": "^1.15.2", "config": "^1.21.0", "express": "^4.14.0", - "leaflet": "^1.0.3", - "leaflet-control-geocoder": "^1.5.4", - "leaflet.markercluster": "^1.0.0", + "leaflet": "1.2.0", + "leaflet-control-geocoder": "1.5.4", + "leaflet.markercluster": "1.0.0", "lodash-es": "^4.17.4", "massive": "^2.5.0", "moment": "^2.17.1", "plyr": "^2.0.11", - "substance": "substance/substance#d0f842bdf54ee982394fab2a93e0aa40fbc78b93", + "substance": "container-annos", "ws": "^1.1.1" }, "devDependencies": { "eslint": "^3.5.0", "font-awesome": "^4.7.0", - "substance-bundler": "^0.15.2", + "substance-bundler": "^0.18.3", "tap-spec": "^4.1.1", "tape": "^4.6.0", "uglify-js": "^2.7.3", @@ -35,5 +35,5 @@ "test": "gulp test" }, "license": "GPL-3.0", - "version": "2.0.0-alpha.1" + "version": "1.0.0-alpha.1" } diff --git a/packages/definition-manager/DefinitionsPage.js b/packages/definition-manager/DefinitionsPage.js index 7ef3dcc..d520dbc 100644 --- a/packages/definition-manager/DefinitionsPage.js +++ b/packages/definition-manager/DefinitionsPage.js @@ -1,5 +1,5 @@ -import { FontAwesomeIcon as Icon, Input } from 'substance' -import { AbstractEntityPage } from 'archivist' +import { FontAwesomeIcon as Icon } from 'substance' +import { AbstractEntityPage } from 'archivist-js' const definitionTypes = [ 'общий комментарий', @@ -10,6 +10,8 @@ const definitionTypes = [ class DefinitionsPage extends AbstractEntityPage { renderFilters($$) { + const Input = this.getComponent('input') + let filters = [] let search = $$('div').addClass('se-search').append( $$(Icon, {icon: 'fa-search'}) diff --git a/packages/definition-manager/package.js b/packages/definition-manager/package.js index b311fac..0672244 100644 --- a/packages/definition-manager/package.js +++ b/packages/definition-manager/package.js @@ -4,5 +4,13 @@ export default { name: 'definition-manager', configure: function(config) { config.addPage(DefinitionsPage.pageName, DefinitionsPage) + config.addLabel('definitions', { + en: 'Definitions', + ru: 'Дефиниции' + }) + config.addLabel('add-definition', { + en: '+ Add Definition', + ru: '+ Добавить дефиницию' + }) } } \ No newline at end of file diff --git a/packages/definition/DefinitionContextItem.js b/packages/definition/DefinitionContextItem.js index 1c732cb..ab0539b 100644 --- a/packages/definition/DefinitionContextItem.js +++ b/packages/definition/DefinitionContextItem.js @@ -17,10 +17,6 @@ class DefinitionContextItem extends Component { let el = $$('div') .attr("data-id", this.props.entityId) .addClass('sc-entity-entry se-definition') - - if(this.props.mode !== 'view') { - el.on('click', this.handleEditorClick) - } if(this.props.focus) { el.addClass('se-focused') @@ -29,11 +25,16 @@ class DefinitionContextItem extends Component { el.append( $$('div').addClass('se-type').append(this.getLabel('definition')), $$('div').addClass('se-title').append(node.name), - $$('div').addClass('se-description').setInnerHTML(node.description), - $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) - .on('click', this.editEntity) + $$('div').addClass('se-description').setInnerHTML(node.description) ) + if(this.props.mode !== 'view') { + el.append( + $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) + .on('click', this.editEntity) + ).on('click', this.handleEditorClick) + } + return el } diff --git a/packages/definition/DefinitionReference.js b/packages/definition/DefinitionReference.js index ca9b588..69d4a7d 100644 --- a/packages/definition/DefinitionReference.js +++ b/packages/definition/DefinitionReference.js @@ -18,6 +18,14 @@ class DefinitionReference extends PropertyAnnotation { isResourceMultipleReference() { return false } + + setHighlighted(highlighted, scope) { + if (this.highlighted !== highlighted) { + this.highlightedScope = scope + this.highlighted = highlighted + this.emit('highlighted', highlighted) + } + } } DefinitionReference.define({ diff --git a/packages/definition/DefinitionTool.js b/packages/definition/DefinitionTool.js deleted file mode 100644 index 8d3a0e4..0000000 --- a/packages/definition/DefinitionTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class DefinitionTool extends AnnotationTool {} - -export default DefinitionTool \ No newline at end of file diff --git a/packages/definition/_index.css b/packages/definition/_index.css index a9548c3..ef2eb4a 100644 --- a/packages/definition/_index.css +++ b/packages/definition/_index.css @@ -31,7 +31,7 @@ } .se-entity-entries .sc-entity-entry.se-definition .se-description { - margin-right: 0px; + margin-right: 20px; } .se-entity-entries .sc-entity-entry.se-definition .se-type, diff --git a/packages/definition/package.js b/packages/definition/package.js index 74cfca7..0bf7035 100644 --- a/packages/definition/package.js +++ b/packages/definition/package.js @@ -1,5 +1,4 @@ import DefinitionReference from './DefinitionReference' -import DefinitionTool from './DefinitionTool' import DefinitionComponent from './DefinitionComponent' import DefinitionCommand from './DefinitionCommand' import DefinitionContextItem from './DefinitionContextItem' @@ -8,7 +7,6 @@ export default { name: 'definition', configure: function(config) { config.addNode(DefinitionReference) - //config.addTool(DefinitionReference.type, DefinitionTool, {toolGroup: 'references'}) config.addCommand(DefinitionReference.type, DefinitionCommand, { nodeType: DefinitionReference.type }) config.addIcon(DefinitionReference.type, {'fontawesome': 'fa-book'}) config.addComponent('definition', DefinitionComponent) diff --git a/packages/documents/DocumentsPage.js b/packages/documents/DocumentsPage.js new file mode 100644 index 0000000..27896a7 --- /dev/null +++ b/packages/documents/DocumentsPage.js @@ -0,0 +1,503 @@ +import { Component, FontAwesomeIcon as Icon, SplitPane, SubstanceError as Err } from 'substance' +import { concat, each, findIndex, isEmpty, isEqual } from 'lodash-es' +import moment from 'moment' + +// Sample data for debugging +// import DataSample from '../../data/docs' + +const documentStates = [ + 'published', + 'finished', + 'verified', + 'transcripted' +] + +class DocumentsPage extends Component { + constructor(...args) { + super(...args) + + this.handleActions({ + 'loadMore': this._loadMore, + 'newDocument': this._createDocument + }) + } + + didMount() { + document.title = this.getLabel('documents') + this._loadData() + } + + didUpdate(oldProps, oldState) { + if(oldState.search !== this.state.search || !isEqual(oldState.filters, this.state.filters) || oldState.direction !== this.state.direction) { + this.searchData() + } + } + + getInitialState() { + return { + filters: { + documentState: 'all' + }, + search: '', + perPage: 30, + order: '"updatedAt"', + direction: 'desc', + pagination: false, + items: [] + } + } + + // willReceiveProps() { + // this._loadData() + // } + + render($$) { + let documentItems = this.state.items + let el = $$('div').addClass('sc-documents') + let main = $$('div').addClass('se-entity-layout') + + let header = this.renderHeader($$) + + let toolbox = this.renderToolbox($$) + main.append(toolbox) + + if (documentItems) { + if (documentItems.length > 0) { + main.append(this.renderFull($$)) + } else { + main.append(this.renderEmpty($$)) + } + } + + el.append( + $$(SplitPane, {splitType: 'vertical', sizeA: '40px'}).append( + header, + main + ) + ) + return el + } + + renderFilters($$) { + const Input = this.getComponent('input') + let filters = [] + let search = $$('div').addClass('se-search').append( + $$(Icon, {icon: 'fa-search'}) + ) + let searchInput = $$(Input, {type: 'search', placeholder: this.getLabel('search-placeholder')}) + .ref('searchInput') + + if(this.isSearchEventSupported()) { + searchInput.on('search', this._onSearch) + } else { + searchInput.on('keypress', this._onSearchKeyPress) + } + search.append(searchInput) + + filters.push(search) + + let documentStateFilter = $$('select').addClass('se-type-filter') + .ref('stateFilter') + .on('change', this._onSearch) + .append($$('option').attr('value', 'all').append( + this.getLabel('select-document-state') + )) + + documentStates.forEach(type => { + let option = $$('option').attr('value', type).append(type) + let documentState = this.state.filters.documentState + if(type === documentState) option.attr('selected', 'selected') + documentStateFilter.append(option) + }) + + filters.push($$('div').addClass('se-select').append(documentStateFilter)) + + let directionIcon = this.state.direction === 'desc' ? 'fa-sort-down' : 'fa-sort-up' + + let createdSort = $$('div').addClass('se-updated-sort se-sort').append( + $$('div').addClass('se-label').append(this.getLabel('sort-updated')) + ).on('click', this._onSort.bind(this, '"updatedAt"')) + + if(this.state.order === '"updatedAt"') { + createdSort.append( + $$('div').addClass('se-icon').append( + $$(Icon, {icon: directionIcon}) + ) + ) + } + + let alphabetSort = $$('div').addClass('se-alphabet-sort se-sort').append( + $$('div').addClass('se-label').append(this.getLabel('sort-alphabet')) + ).on('click', this._onSort.bind(this, 'title')) + + if(this.state.order === 'title') { + alphabetSort.append( + $$('div').addClass('se-icon').append( + $$(Icon, {icon: directionIcon}) + ) + ) + } + + filters.push( + $$('div').addClass('se-sort-wrapper').append(createdSort, alphabetSort) + ) + + return filters + } + + renderHeader($$) { + let Header = this.getComponent('header') + return $$(Header, {page: 'archive'}) + } + + renderToolbox($$) { + let Toolbox = this.getComponent('toolbox') + let filters = this.renderFilters($$) + + let toolbox = $$(Toolbox, { + actions: { + 'newDocument': this.getLabel('add-document') + }, + content: filters + }) + + return toolbox + } + + renderStatusBar($$) { + let componentRegistry = this.context.componentRegistry + let StatusBar = componentRegistry.get('status-bar') + + return $$(StatusBar) + } + + renderEmpty($$) { + const Layout = this.getComponent('layout') + let layout = $$(Layout, { + width: 'medium', + textAlign: 'center' + }) + + if(this.state.total === 0) { + layout.append( + $$('h1').html( + 'No results' + ), + $$('p').html('Sorry, no entities matches your query') + ) + } else { + let Spinner = this.getComponent('spinner') + layout.append($$(Spinner, {message: 'spinner-loading'})) + } + + return layout + } + + renderAdditionalMenu($$, actions) { + const Button = this.getComponent('button') + let el = $$('div').addClass('se-more').attr({'tabindex': 0}) + let actionsList = $$('ul').addClass('se-more-content') + each(actions, action => { + actionsList.append( + $$('li').addClass('se-more-item').append( + $$(Button, {label: action.label}).on('click', action.action) + ) + ) + }) + el.append(actionsList) + + return el + } + + renderFull($$) { + let urlHelper = this.context.urlHelper + let items = this.state.items + let total = this.state.total + let Pager = this.getComponent('pager') + let Grid = this.getComponent('grid') + let grid = $$(Grid) + + if (items) { + items.forEach(function(item, index) { + let url = urlHelper.openDocument(item.documentId) + let documentIcon = $$(Icon, {icon: 'fa-file-text-o'}) + let title = $$('a').attr({href: url}).append(item.title) + let updatedFromNow = moment(item.updatedAt).fromNow() + let updatedDateTime = moment(item.updatedAt).format('DD.MM.YYYY HH:mm') + let updatedAt = this.getLabel('updated-info') + .replace('fromnow', updatedFromNow) + .replace('datetime', updatedDateTime) + .replace('username', item.updatedBy) + let className = item.summary ? 'se-expanded' : '' + + let additionalActions = [ + {label: this.getLabel('delete-action'), action: this._removeItem.bind(this, item.documentId)}, + ] + + let row = $$(Grid.Row).addClass('se-document-meta ' + className).ref(item.documentId).append( + $$(Grid.Cell, {columns: 1}).addClass('se-badge').append(documentIcon), + $$(Grid.Cell, {columns: 5}).addClass('se-title').append(title), + $$(Grid.Cell, {columns: 3}).append(updatedAt), + $$(Grid.Cell, {columns: 2}).append(item.count ? item.count + ' fragments' : item.meta.state), + $$(Grid.Cell, {columns: 1}).addClass('se-additional').append( + this.renderAdditionalMenu($$, additionalActions) + ).on('click', function(e) { + e.stopPropagation() + }) + ).on('click', this._loadFragments.bind(this, item.documentId, index)) + + if(item.summary) { + row.append( + $$(Grid.Row).addClass('se-document-summary').append( + $$(Grid.Cell, {columns: 12}).addClass('se-summary').append(item.summary) + ) + ) + } + + grid.append(row) + + if(this.state.details === index && item.fragments) { + item.fragments.forEach(function(fragment) { + let fragmentIcon = $$(Icon, {icon: 'fa-comments-o'}) + grid.append( + $$(Grid.Row).addClass('se-document-fragment').append( + $$(Grid.Cell, {columns: 1}).addClass('se-badge').append(fragmentIcon), + $$(Grid.Cell, {columns: 11}).addClass('se-fragment').append($$('p').setInnerHTML(fragment.content)) + ) + ) + }) + } + }.bind(this)) + } + + if(total > this.state.perPage) { + grid.append( + $$(Pager, { + total: total, + loaded: items.length + }) + ) + } + + return grid + } + + _createDocument() { + let authClient = this.context.authenticationClient + let documentClient = this.context.documentClient + let user = authClient.getUser() + + documentClient.createDocument({ + schemaName: 'archivist-interview', + schemaVersion: '1.0.0', + info: { + title: 'Untitled', + userId: user.userId + } + }, (err, result) => { + this.send('navigate', { + page: 'documents', + documentId: result.documentId + }) + }) + } + + _removeItem(id) { + let documentClient = this.context.documentClient + documentClient.deleteDocument(id, err => { + if(err) console.error(err) + this._loadData() + }) + } + + /* + Search documents + */ + searchData() { + let searchValue = this.state.search + + if(isEmpty(searchValue)) { + return this._loadData() + } + + let language = 'russian' + let filters = this.state.filters + let dataFilters = {} + if(filters.documentState) { + dataFilters = {'meta->>state': filters.documentState} + } + let perPage = this.state.perPage + let pagination = this.state.pagination + let options = { + limit: perPage, + offset: pagination ? this.state.items.length : 0 + } + let items = [] + let documentClient = this.context.documentClient + documentClient.searchDocuments(searchValue, language, dataFilters, options, function(err, docs) { + if (err) { + this.setState({ + error: new Err('DocumentsPage.SearchError', { + message: 'Search results could not be loaded.', + cause: err + }) + }) + console.error('ERROR', err) + return + } + + let details = findIndex(docs.records, function(record) { + return record.fragments + }) + + if(pagination) { + items = concat(this.state.items, docs.records) + } else { + items = docs.records + } + + this.extendState({ + items: items, + total: parseInt(docs.total, 10), + details: details + }) + }.bind(this)) + } + + /* + Load more data + */ + _loadMore() { + this.extendState({ + pagination: true + }) + this.searchData() + } + + /* + Loads documents + */ + _loadData() { + let filters = this.state.filters + let dataFilters = {} + if(filters.documentState && filters.documentState !== 'all') { + dataFilters = {'meta->>state': filters.documentState} + } + let pagination = this.state.pagination + let perPage = this.state.perPage + let options = { + limit: perPage, + offset: pagination ? this.state.items.length : 0, + order: this.state.order + ' ' + this.state.direction + } + let items = [] + + let documentClient = this.context.documentClient + + documentClient.listDocuments(dataFilters, options, function(err, docs) { + if (err) { + this.setState({ + error: new Err('DocumentsPage.LoadingError', { + message: 'Documents could not be loaded.', + cause: err + }) + }) + console.error('ERROR', err) + return + } + + if(pagination) { + items = concat(this.state.items, docs.records) + } else { + items = docs.records + } + + this.extendState({ + items: items, + total: parseInt(docs.total, 10) + }) + }.bind(this)) + } + + _loadFragments(documentId, index) { + let searchValue = this.state.search + + if(isEmpty(searchValue)) { + return + } + + let language = 'russian' + let filters = {} + let options = {} + let documentClient = this.context.documentClient + let items = this.state.items + + if(!items[index].fragments) { + documentClient.searchFragments(documentId, searchValue, language, filters, options, function(err, fragments) { + if (err) { + this.setState({ + error: new Err('DocumentsPage.FragmentsSearchError', { + message: 'Search results could not be loaded.', + cause: err + }) + }) + console.error('ERROR', err) + return + } + + items[index].fragments = fragments + + this.extendState({ + items: items, + details: index + }) + }.bind(this)) + } else { + this.extendState({details: index}) + } + } + + _onSearchKeyPress(e) { + // Perform search query on pressing enter + if (e.which === 13 || e.keyCode === 13) { + let searchValue = this.refs['searchInput'].val() + this.extendState({ + search: searchValue, + pagination: false + }) + return false; + } + } + + _onSearch() { + let searchValue = this.refs['searchInput'].val() + let documentStateValue = this.refs['stateFilter'].val() + let filters = {} + filters['documentState'] = documentStateValue + if(documentStateValue === 'all') delete filters['documentState'] + this.extendState({ + filters: filters, + search: searchValue, + pagination: false + }) + } + + _onSort(order) { + const currentDirection = this.state.direction + let direction = currentDirection === 'asc' ? 'desc' : 'asc' + this.extendState({ + direction: direction, + order: order + }) + } + + isSearchEventSupported() { + let element = document.createElement('input') + let eventName = 'onsearch' + let isSupported = (eventName in element) + + return isSupported + } +} + +export default DocumentsPage diff --git a/packages/documents/_index.css b/packages/documents/_index.css new file mode 100644 index 0000000..0b1b3d9 --- /dev/null +++ b/packages/documents/_index.css @@ -0,0 +1,53 @@ +.sc-documents .se-entity-layout { + display: flex; + flex-flow: column nowrap; +} + +.sc-documents .sc-grid .sc-grid { + overflow: inherit; +} + +.sc-documents .se-document-summary { + border-bottom: none; +} + +.sc-documents .se-document-fragment { + display: table; + width: 100%; +} + +.sc-documents > .sc-grid > .se-row.se-document-fragment > .se-cell { + display: table-cell; + float: none; + vertical-align: middle; +} + +.sc-documents .se-document-fragment .se-badge { + background: #fafafa; + text-align: center; +} + +.sc-documents .se-document-fragment:hover > .se-badge { + background: #EEEEEE; +} + +.sc-documents .sc-grid > .se-document-fragment:before, .sc-documents .sc-grid > .se-document-fragment:after { + content: none; + display: table; +} + +.sc-documents .se-sort { + display: flex; + align-items: center; + float: right; + padding: 3px 10px; + margin: 0px 10px; +} + +.sc-documents .se-sort:hover { + cursor: pointer; +} + +.sc-documents .se-sort .se-label { + margin-right: 5px; +} diff --git a/packages/documents/package.js b/packages/documents/package.js new file mode 100644 index 0000000..afe81a3 --- /dev/null +++ b/packages/documents/package.js @@ -0,0 +1,32 @@ +import DocumentsPage from './DocumentsPage' + +export default { + name: 'archivist-documents', + configure: function(config) { + config.addPage('archive', DocumentsPage) + config.addLabel('archive', { + en: 'Documents', + ru: 'Документы' + }) + config.addLabel('documents', { + en: 'Documents', + ru: 'Документы' + }) + config.addLabel('add-document', { + en: '+ New Document', + ru: '+ Добавить документ' + }) + config.addLabel('select-document-state', { + en: 'Select state', + ru: 'Статус документа' + }) + config.addLabel('sort-updated', { + en: 'Sort by date', + ru: 'По дате' + }) + config.addLabel('sort-alphabet', { + en: 'Sort by title', + ru: 'По названию' + }) + } +} diff --git a/packages/engine/document/DocumentEngine.js b/packages/engine/document/DocumentEngine.js index fd3eee7..1f21114 100644 --- a/packages/engine/document/DocumentEngine.js +++ b/packages/engine/document/DocumentEngine.js @@ -1,4 +1,4 @@ -let ArchivistDocumentEngine = require('archivist').DocumentEngine +let ArchivistDocumentEngine = require('archivist-js').DocumentEngine let Err = require('substance').SubstanceError let filter = require('lodash/filter') let forEach = require('lodash/forEach') diff --git a/packages/engine/package.js b/packages/engine/package.js index a693308..5713948 100644 --- a/packages/engine/package.js +++ b/packages/engine/package.js @@ -1,12 +1,13 @@ -let ArchivistStorePackage = require('archivist').ArchivistStorePackage +let ArchivistStorePackage = require('archivist-js').ArchivistStorePackage module.exports = { name: 'engine', configure: function(config) { config.import(ArchivistStorePackage); config.import(require('./mailer/package')) - config.import(require('archivist').AuthEnginePackage) - config.import(require('archivist').SnapshotEnginePackage) + config.import(require('archivist-js').AuthEnginePackage) + config.import(require('archivist-js').CollabEnginePackage) + config.import(require('archivist-js').SnapshotEnginePackage) config.import(require('./document/package')) config.import(require('./resource/package')) } diff --git a/packages/engine/resource/ResourceEngine.js b/packages/engine/resource/ResourceEngine.js index bc3f373..9328868 100644 --- a/packages/engine/resource/ResourceEngine.js +++ b/packages/engine/resource/ResourceEngine.js @@ -1,5 +1,5 @@ let Err = require('substance').SubstanceError -let ArchivistResourceEngine = require('archivist').ResourceEngine +let ArchivistResourceEngine = require('archivist-js').ResourceEngine let each = require('lodash/each') let isEmpty = require('lodash/isEmpty') let isNull = require('lodash/isNull') @@ -17,7 +17,7 @@ class ResourceEngine extends ArchivistResourceEngine { let query = ` SELECT "entityId", "name", data->'parent' AS parent, data->'position' AS position FROM entities - WHERE "entityType" = $1 + WHERE "entityType" = $1 ORDER BY cast(data->>'position' as integer) ASC ` @@ -28,7 +28,7 @@ class ResourceEngine extends ArchivistResourceEngine { cause: err })) } - + resolve(entities) }) }) @@ -37,7 +37,7 @@ class ResourceEngine extends ArchivistResourceEngine { updateResourcesTree(data) { return Promise.map(data, entity => { return this.updateEntity(entity.entityId, entity) - }) + }) } getResourcesTreeFacets(filters, entityType) { @@ -73,7 +73,7 @@ class ResourceEngine extends ArchivistResourceEngine { WHERE "entityType" = '${entityType}' ORDER BY pos ASC ` - + return new Promise((resolve, reject) => { this.db.run(query, where.params, (err, entities) => { if (err) { @@ -81,7 +81,7 @@ class ResourceEngine extends ArchivistResourceEngine { cause: err })) } - + resolve(entities) }) }) @@ -89,12 +89,12 @@ class ResourceEngine extends ArchivistResourceEngine { getLocationsList() { let query = ` - SELECT "entityId", name, "entityType", data, - (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId") AS cnt, - (SELECT SUM(("references"->"entityId")::text::integer) FROM documents WHERE "references" ? "entityId") AS sum + SELECT "entityId", name, "entityType", data, + (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published') AS cnt, + (SELECT SUM(("references"->"entityId")::text::integer) FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published') AS sum FROM entities - WHERE ("entityType" = 'prison' OR "entityType" = 'toponym') - AND (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId") > 0 + WHERE (select exists(SELECT 1 FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published')) + AND ("entityType" = 'prison' OR "entityType" = 'toponym') AND (data->>'point' != '{}') ` return new Promise((resolve, reject) => { @@ -104,7 +104,7 @@ class ResourceEngine extends ArchivistResourceEngine { cause: err })) } - + let geojson = { type: "FeatureCollection", features: [] @@ -123,7 +123,7 @@ class ResourceEngine extends ArchivistResourceEngine { feature.properties.documents = entity.cnt feature.properties.fragments = entity.sum - if(!isNull(entity.data.point)) geojson.features.push(feature) + if(!isNull(entity.data.point) && entity.data.point.length > 0) geojson.features.push(feature) }) resolve(geojson) @@ -139,20 +139,20 @@ class ResourceEngine extends ArchivistResourceEngine { if(letter !== 'undefined') letterCondition = 'AND lower(LEFT(name, 1)) = \'' + letter + '\'' let countQuery = ` - SELECT COUNT(*) + SELECT COUNT(*) FROM entities - WHERE "entityType" = 'person' + WHERE "entityType" = 'person' AND entities.data->'global' = 'true' ${letterCondition} ` let query = ` - SELECT "entityId", name, description, + SELECT "entityId", name, description, (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published') AS count, (SELECT SUM(("references"->"entityId")::text::integer) FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published') AS fragments FROM entities - WHERE "entityType" = 'person' + WHERE (select exists(SELECT 1 FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published')) + AND "entityType" = 'person' AND entities.data->'global' = 'true' ${letterCondition} - AND (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId") > 0 ORDER BY name ASC LIMIT ${limit} OFFSET ${offset} ` @@ -176,7 +176,7 @@ class ResourceEngine extends ArchivistResourceEngine { total: count[0].count, records: entities } - + resolve(results) }) }) @@ -187,9 +187,9 @@ class ResourceEngine extends ArchivistResourceEngine { let query = ` SELECT lower(LEFT(name, 1)) AS letter, COUNT(*) AS cnt FROM entities - WHERE "entityType" = 'person' + WHERE (select exists(SELECT 1 FROM documents WHERE "references" ? "entityId" AND meta->>'state' = 'published')) + AND "entityType" = 'person' AND data->>'global' = 'true' - AND (SELECT COUNT(*) FROM documents WHERE "references" ? "entityId") > 0 GROUP BY letter ` @@ -200,7 +200,7 @@ class ResourceEngine extends ArchivistResourceEngine { cause: err })) } - + resolve(stats) }) }) diff --git a/packages/entity-reference/EntityReferenceTool.js b/packages/entity-reference/EntityReferenceTool.js deleted file mode 100644 index 8cfa809..0000000 --- a/packages/entity-reference/EntityReferenceTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class EntityReferenceTool extends AnnotationTool {} - -export default EntityReferenceTool \ No newline at end of file diff --git a/packages/entity-reference/package.js b/packages/entity-reference/package.js index e4014ef..e3d953f 100644 --- a/packages/entity-reference/package.js +++ b/packages/entity-reference/package.js @@ -1,18 +1,15 @@ -import { platform } from 'substance' -import EntityReferenceTool from './EntityReferenceTool' import EntityReferenceCommand from './EntityReferenceCommand' export default { name: 'entity-reference', configure: function(config) { - config.addTool('entity-reference', EntityReferenceTool, {toolGroup: 'references'}) - config.addCommand('entity-reference', EntityReferenceCommand, { nodeType: 'entity-reference' }) + config.addCommand('entity-reference', EntityReferenceCommand, { nodeType: 'entity-reference', commandGroup: 'references' }) config.addIcon('entity-reference', {'fontawesome': 'fa-book'}) + config.addLabel('entity-reference', { + en: 'entity reference', + ru: 'связать с сущностью' + }) - if (platform.isMac) { - config.addKeyboardShortcut('cmd+e', { command: 'entity-reference' }) - } else { - config.addKeyboardShortcut('ctrl+e', { command: 'entity-reference' }) - } + config.addKeyboardShortcut('CommandOrControl+E', { command: 'entity-reference' }) } } \ No newline at end of file diff --git a/packages/explorer/DocumentItem.js b/packages/explorer/DocumentItem.js index 5df9cab..ab5e720 100644 --- a/packages/explorer/DocumentItem.js +++ b/packages/explorer/DocumentItem.js @@ -1,15 +1,16 @@ -import { Component, Grid } from 'substance' +import { Component } from 'substance' import { forEach, isEmpty } from 'lodash-es' import moment from 'moment' class DocumentItem extends Component { render($$) { + let Grid = this.getComponent('grid') + let item = this.props.item let meta = item.meta let index = this.props.index let config = this.context.config - //let documentIcon = $$(Icon, {icon: 'fa-file-text-o'}) let urlHelper = this.context.urlHelper let url = urlHelper.openDocument(item.documentId) diff --git a/packages/explorer/Explorer.js b/packages/explorer/Explorer.js index 0ae3c2d..56d4405 100644 --- a/packages/explorer/Explorer.js +++ b/packages/explorer/Explorer.js @@ -1,4 +1,4 @@ -import { Component, Grid, Layout, SplitPane } from 'substance' +import { Component, SplitPane } from 'substance' import { clone, concat, each, extend, findIndex, findLastIndex, isEmpty, isEqual, isUndefined } from 'lodash-es' // Sample data for debugging @@ -27,12 +27,6 @@ class Explorer extends Component { this._loadTopics() } - // didUpdate(oldProps, oldState) { - // // if(oldState.search !== this.state.search) { - // // this.searchData() - // // } - // } - willUpdateState(state) { let oldFilters = this.state.filters let newFilters = state.filters @@ -80,6 +74,8 @@ class Explorer extends Component { } render($$) { + let Layout = this.getComponent('layout') + let documentItems = this.state.items let el = $$('div').addClass('sc-explorer') @@ -148,6 +144,8 @@ class Explorer extends Component { } renderEmpty($$) { + let Layout = this.getComponent('layout') + let layout = $$(Layout, { width: 'medium', textAlign: 'center' @@ -177,6 +175,8 @@ class Explorer extends Component { } renderFull($$) { + let Grid = this.getComponent('grid') + let items = this.state.items let total = this.state.total let resource = this.state.resource @@ -447,7 +447,8 @@ class Explorer extends Component { if(op === ' ~~') metaFilter[prop] = '%' + value + '%' this.extendState({ filters: extend({}, filters, metaFilter), - metaFilters: metaFilters + metaFilters: metaFilters, + resource: false }) } @@ -463,7 +464,8 @@ class Explorer extends Component { metaFilters[id] = value this.extendState({ filters: extend({}, filters, metaFilter), - metaFilters: metaFilters + metaFilters: metaFilters, + resource: false }) } @@ -478,7 +480,8 @@ class Explorer extends Component { if(filterKey2) delete filters[filterKeys[filterKey2]] this.extendState({ filters: extend({}, filters), - metaFilters: metaFilters + metaFilters: metaFilters, + resource: false }) } @@ -495,7 +498,8 @@ class Explorer extends Component { }) this.extendState({ filters: extend({}, filters), - metaFilters: metaFilters + metaFilters: metaFilters, + resource: false }) } diff --git a/packages/explorer/ResourceReference.js b/packages/explorer/ResourceReference.js index 020d5fc..2f7a632 100644 --- a/packages/explorer/ResourceReference.js +++ b/packages/explorer/ResourceReference.js @@ -1,4 +1,4 @@ -import { Component, Grid } from 'substance' +import { Component } from 'substance' class ResourceReference extends Component { @@ -87,6 +87,7 @@ class ResourceReference extends Component { } renderDocumentList($$) { + let Grid = this.getComponent('grid') let items = this.state.documents let resource = this.state.resource let isTopic = resource ? resource.entityType === 'subject' : false diff --git a/packages/explorer/SearchBar.js b/packages/explorer/SearchBar.js index 07d380f..db975b8 100644 --- a/packages/explorer/SearchBar.js +++ b/packages/explorer/SearchBar.js @@ -1,8 +1,11 @@ -import { Button, Component, Input } from 'substance' +import { Component } from 'substance' class SearchBar extends Component { render($$) { + const Button = this.getComponent('button') + const Input = this.getComponent('input') + let el = $$('div').addClass('sc-searchbar') let inputSearch = $$(Input, {placeholder: this.getLabel('searchbar-placeholder'), value: this.props.value}) @@ -10,7 +13,7 @@ class SearchBar extends Component { .addClass('se-search-input') .on('keypress', this._onSearchKeyPress) - let submitBtn = $$(Button, {style: 'outline', label: 'searchbar-submit', icon: 'searchbar-search'}) + let submitBtn = $$(Button, {theme: 'round', label: 'searchbar-submit', icon: 'searchbar-search'}) .addClass('se-search-submit') .on('click', this._onSearch) diff --git a/packages/explorer/_index.css b/packages/explorer/_index.css index 80322d5..835e349 100644 --- a/packages/explorer/_index.css +++ b/packages/explorer/_index.css @@ -24,12 +24,19 @@ position: absolute; top: 5px; right: 6px; + height: 30px; + line-height: 30px; background: #4A90E2; padding: 0px 10px; color: #fff; border-radius: 3px; } +.sc-explorer .sc-searchbar .se-search-submit:hover, +.sc-explorer .sc-searchbar .se-search-submit:active { + background: #4A90E2!important; +} + .sc-explorer .se-results-pane { padding-top: 30px; } diff --git a/packages/forms/OstForms.js b/packages/forms/OstForms.js index f24bfab..900e734 100644 --- a/packages/forms/OstForms.js +++ b/packages/forms/OstForms.js @@ -1,5 +1,5 @@ import { DefaultDOMElement } from 'substance' -import { Forms } from 'archivist' +import { Forms } from 'archivist-js' import GeocodedField from './geocoded-field/GeocodedField' import MapField from './map-field/MapField' @@ -7,9 +7,12 @@ export default class OstForms extends Forms { addGeocodedField(fieldId, el, config) { config = config || {} el = DefaultDOMElement.wrapNativeElement(el) + let configurator = this.configurator + let field = GeocodedField.mount({ fieldId, - config + config, + configurator }, el) field.on('commit', this._onCommit, this) field.on('geocode', this._onGeocode, this) @@ -20,9 +23,12 @@ export default class OstForms extends Forms { addMap(fieldId, el, config) { config = config || {} el = DefaultDOMElement.wrapNativeElement(el) + let configurator = this.configurator + let field = MapField.mount({ fieldId, - config + config, + configurator }, el) field.on('commit', this._onCommit, this) field.on('map:point', this._onSetPoint, this) diff --git a/packages/forms/OstNodeForm.js b/packages/forms/OstNodeForm.js index 910509e..7f674b6 100644 --- a/packages/forms/OstNodeForm.js +++ b/packages/forms/OstNodeForm.js @@ -1,4 +1,4 @@ -import { NodeForm } from 'archivist' +import { NodeForm } from 'archivist-js' import { each } from 'lodash-es' import OstForms from './OstForms' @@ -6,7 +6,7 @@ class OstNodeForm extends NodeForm { constructor(...args) { super(...args) - this.forms = new OstForms() + this.forms = new OstForms({configurator: this.context.configurator}) } didMount() { @@ -72,8 +72,14 @@ class OstNodeForm extends NodeForm { } field = this.forms._editables.currentName if(field) { + const currentName = this.forms.getValue('currentName') + let synonyms = this.forms.getValue('synonyms') + const currentNameIndex = synonyms.indexOf(currentName) + synonyms[currentNameIndex] = value + this.forms.setValue('synonyms', synonyms) this.forms.setValue('currentName', value) this._commit('currentName', value) + this.forms._editables.synonyms.rerender() } } } diff --git a/packages/forms/geocoded-field/GeocodedField.js b/packages/forms/geocoded-field/GeocodedField.js index a89d53e..1695846 100644 --- a/packages/forms/geocoded-field/GeocodedField.js +++ b/packages/forms/geocoded-field/GeocodedField.js @@ -1,4 +1,6 @@ -import { Component, FontAwesomeIcon as Icon, Input } from 'substance' +import { Component, FontAwesomeIcon as Icon, InputPackage } from 'substance' + +const { Input } = InputPackage class GeocodedField extends Component { diff --git a/packages/forms/map-field/MapField.js b/packages/forms/map-field/MapField.js index 4057cb7..d763e76 100644 --- a/packages/forms/map-field/MapField.js +++ b/packages/forms/map-field/MapField.js @@ -32,13 +32,13 @@ class MapField extends Component { let map = $$('div').addClass('se-map') .attr({id: 'map'}) .ref('map') - + el.append( map, $$('div').attr({id: 'geocode-selector'}), $$('div').addClass('help').append(config.placeholder) ) - + return el } @@ -46,7 +46,7 @@ class MapField extends Component { // TODO: fix array reverse bug, sometimes coordinates got reversed on map update if(value) { if(value.length > 0) { - this.map.setView(value.reverse(), this.state.defaultZoom) + this.map.setView({lat: value[0], lng: value[1]}, this.state.defaultZoom) this.control._geocodeMarker = L.marker(value).addTo(this.map) } } @@ -54,7 +54,7 @@ class MapField extends Component { getValue() { let latlng = this.control._geocodeMarker.getLatLng() - return [latlng.lng, latlng.lat] + return [latlng.lat, latlng.lng] } geocode(value) { @@ -155,4 +155,4 @@ class MapField extends Component { } -export default MapField \ No newline at end of file +export default MapField diff --git a/packages/indexer/Indexer.js b/packages/indexer/Indexer.js index 155b8ad..e2e89f1 100644 --- a/packages/indexer/Indexer.js +++ b/packages/indexer/Indexer.js @@ -1,14 +1,21 @@ let Err = require('substance').SubstanceError -let ArchivistIndexer = require('archivist').Indexer +let ArchivistIndexer = require('archivist-js').Indexer let findIndex = require('lodash/findIndex') let forEach = require('lodash/forEach') let isEmpty = require('lodash/isEmpty') // Massive internal libs -let ArgTypes = require('../../node_modules/massive/lib/arg_types') -let Where = require('../../node_modules/massive/lib/where') +let massivePath = require.resolve('massive') +let ArgTypes = require(massivePath + '/../lib/arg_types') +let Where = require(massivePath + '/../lib/where') class Indexer extends ArchivistIndexer { + constructor(config) { + super(config) + + this.resourceEngine = config.resourceEngine + } + searchDocuments(filters, options) { let isTextSearch = filters.query ? true : false let limit = options.limit || 100 @@ -243,6 +250,43 @@ ORDER BY count DESC limit ${limit} offset ${offset}` }) }.bind(this)) } + + _countEntityReferences(doc) { + return new Promise((resolve) => { + let configurator = this.configurator.getConfigurator('archivist-subjects') + + this.resourceEngine.getResourcesTree('subject') + .then(subjectsData => { + let importer = configurator.createImporter('subjects') + let subjects = importer.importDocument(subjectsData, true) + let entitiesIndex = doc.getIndex('entities') + let annotations = [] + let references = {} + forEach(entitiesIndex.byReference, (refs, key) => { + annotations.push(key) + references[key] = Object.keys(refs).length + + let parents = subjects.getParents(key) + parents.forEach(parent => { + if(annotations.indexOf(parent) === -1) { + annotations.push(parent) + } + let counter = references[parent] + if(!counter) { + references[parent] = 1 + } else { + references[parent]++ + } + }) + }) + + resolve({ + annotations: annotations, + references: references + }) + }) + }) + } } module.exports = Indexer \ No newline at end of file diff --git a/packages/indexer/package.js b/packages/indexer/package.js index b7db918..0ca272c 100644 --- a/packages/indexer/package.js +++ b/packages/indexer/package.js @@ -8,8 +8,9 @@ module.exports = { db: db, configurator: config, documentEngine: config.getEngine('document'), - snapshotEngine: config.getEngine('snapshot'), - fragmentStore: config.getStore('fragment') + fragmentStore: config.getStore('fragment'), + resourceEngine: config.getEngine('resource'), + snapshotEngine: config.getEngine('snapshot') }) config.addEngine('indexer', indexer) diff --git a/packages/info-context/InfoContext.js b/packages/info-context/InfoContext.js index d7a55e5..a53ef87 100644 --- a/packages/info-context/InfoContext.js +++ b/packages/info-context/InfoContext.js @@ -1,4 +1,4 @@ -import { Button, Component, ScrollPane } from 'substance' +import { Component, ScrollPane } from 'substance' class InfoContext extends Component { getInitialState() { @@ -45,20 +45,22 @@ class InfoContext extends Component { } renderAbstract($$, abstract) { + const Button = this.getComponent('button') + let el = $$('div').addClass('se-meta-abstract') if(this.state.teaserAbstract) { let teaser = abstract.split('\n').shift() el.append( teaser, - $$(Button, {style: 'outline', label: 'expand-abstract'}).addClass('se-abstract-toggle') + $$(Button, {theme: 'round', label: 'expand-abstract'}).addClass('se-abstract-toggle') .on('click', this._toggleAbstract) ) } else { el.append( $$('div').addClass('se-abstract-full') .setInnerHTML('

' + abstract.split('\n').join('

') + '

'), - $$(Button, {style: 'outline', label: 'collapse-abstract'}).addClass('se-abstract-toggle') + $$(Button, {theme: 'round', label: 'collapse-abstract'}).addClass('se-abstract-toggle') .on('click', this._toggleAbstract) ) } diff --git a/packages/interview/Interview.js b/packages/interview/Interview.js index 5ef75ec..ebf70dc 100644 --- a/packages/interview/Interview.js +++ b/packages/interview/Interview.js @@ -1,17 +1,15 @@ import { Document } from 'substance' -import { EntityIndex } from 'archivist' +import { EntityIndex } from 'archivist-js' import { map } from 'lodash-es' /* Archivist Interview model. */ class Interview extends Document { - constructor(...args) { - super(...args) - this._initialize() - } - + _initialize() { + super._initialize() + this.create({ type: 'container', id: 'body', diff --git a/packages/interview/MetaNode.js b/packages/interview/MetaNode.js index b8d9d59..cdf836a 100644 --- a/packages/interview/MetaNode.js +++ b/packages/interview/MetaNode.js @@ -13,7 +13,7 @@ MetaNode.define({ abstract: { type: 'string', default: '', field: { editor: "multitext", description: "Enter abstract", collapse: 'Russian', group: 'Abstract'}}, abstract_translation: { type: 'string', default: '', field: { editor: "multitext", description: "Enter short summary in English", collapse: 'English', group: 'Abstract'}}, abstract_translation_second: { type: 'string', default: '', field: { editor: "multitext", description: "Enter short summary in German", collapse: 'German', group: 'Abstract'}}, - + // Interviewee data title: { type: 'string', default: 'Untitled Interview', field: { editor: "text", description: "Person name", group: 'Person details'}}, interviewee_bio: { type: 'string', default: '', field: { editor: "multitext", description: "Enter person biography", collapse: 'Russian bio', group: 'Person details'}}, @@ -26,10 +26,12 @@ MetaNode.define({ interviewee_military_service: { type: 'boolean', default: false, field: { editor: "logical", description: "Check if person was in Soviet army", label: "Military service", group: 'Person details'}}, interviewee_sex: { type: 'string', default: '', field: { editor: "select", description: "Person gender", options: ['мужчина', 'женщина'], group: 'Person details'}}, interviewee_place_of_birth: { type: 'string', default: '', field: { editor: "text", description: "Person place of birth", group: 'Person details'}}, - interviewee_year_of_birth: { type: 'string', default: '', field: { editor: "input", dataType: "number", description: "Person year of birth", group: 'Person details'}}, - interviewee_enslaving_year: { type: 'string', default: '', field: { editor: "input", dataType: "number", description: "Person enslaving year", group: 'Person details'}}, - interviewee_homecoming_year: { type: 'string', default: '', field: { editor: "input", dataType: "number", description: "Person homecoming year", group: 'Person details'}}, + interviewee_year_of_birth: { type: 'string', default: '', field: { editor: "input", dataType: "text", description: "Person year of birth", group: 'Person details'}}, + interviewee_enslaving_year: { type: 'string', default: '', field: { editor: "input", dataType: "text", description: "Person enslaving year", group: 'Person details'}}, + interviewee_homecoming_year: { type: 'string', default: '', field: { editor: "input", dataType: "text", description: "Person homecoming year", group: 'Person details'}}, interviewee_photo: { type: 'string', default: '', field: { editor: "text", description: "Path to photo file", group: 'Person details'}}, + // TODO: waypoint editor + interviewee_waypoints: { type: ['waypoint'], default: [] }, // Project data project: { type: 'string', default: '', field: { editor: "select", description: "Project", options: ['Международный проект документации рабского и принудительного труда', 'Выжившие в Маутхаузене', 'Коллекция №1'], group: 'Project details'}}, @@ -46,13 +48,10 @@ MetaNode.define({ interview_duration: { type: 'number', default: 0, field: { editor: "input", dataType: "number", description: "Duration (in minutes)", group: 'Project details'}}, media_id: { type: 'string', default: '', field: { editor: "text", description: "Media identifier", group: 'Project details'}}, - // TODO: waypoints - //interviewee_waypoints: { type: ['waypoint'], default: [] } - // Document data published_on: { type: 'string', default: '', field: { editor: "input", dataType: "date", description: "Published date (yyyy-MM-dd)", group: 'Document details'}}, // states: transcripted, verified, finished, published state: { type: 'string', default: '', field: { editor: "select", description: "Document state", options: ['transcripted', 'verified', 'finished', 'published'], group: 'Document details'}} }) -export default MetaNode \ No newline at end of file +export default MetaNode diff --git a/packages/interview/package.js b/packages/interview/package.js index ab2c216..6bb4632 100644 --- a/packages/interview/package.js +++ b/packages/interview/package.js @@ -3,7 +3,8 @@ import MetaNode from './MetaNode' import InterviewSeed from './InterviewSeed' import { BasePackage, ParagraphPackage, PersistencePackage, HeadingPackage, BlockquotePackage, LinkPackage, EmphasisPackage, StrongPackage} from 'substance' -import { CommentPackage, TimecodePackage } from 'archivist' +import { CommentPackage, TimecodePackage } from 'archivist-js' +import WaypointPackage from '../waypoint/package' import SubjectPackage from '../subject/package' import DefinitionPackage from '../definition/package' import PersonPackage from '../person/package' @@ -16,7 +17,8 @@ export default { configure: function(config) { config.defineSchema({ name: 'archivist-interview', - ArticleClass: Interview, + version: '1.0.0', + DocumentClass: Interview, defaultTextType: 'paragraph' }) config.addNode(MetaNode) @@ -24,7 +26,6 @@ export default { // Import Substance Core packages config.import(BasePackage) - config.import(PersistencePackage) config.import(ParagraphPackage) config.import(HeadingPackage) config.import(BlockquotePackage) @@ -33,6 +34,7 @@ export default { config.import(LinkPackage) // Import archivist specific packages + config.import(WaypointPackage) config.import(CommentPackage) config.import(TimecodePackage) config.import(SubjectPackage) @@ -41,5 +43,26 @@ export default { config.import(PrisonPackage) config.import(ToponymPackage) config.import(EntityReferencePackage) + + config.addLabel('undo', { + en: 'undo', + ru: 'отмена' + }) + config.addLabel('redo', { + en: 'redo', + ru: 'вернуть' + }) + config.addLabel('strong', { + en: 'strong', + ru: 'жирный' + }) + config.addLabel('emphasis', { + en: 'emphasis', + ru: 'наклонный' + }) + config.addLabel('link', { + en: 'link', + ru: 'ссылка' + }) } -} \ No newline at end of file +} diff --git a/packages/map/MapBrowser.js b/packages/map/MapBrowser.js index 919a18e..c7c675b 100644 --- a/packages/map/MapBrowser.js +++ b/packages/map/MapBrowser.js @@ -24,11 +24,11 @@ class MapBrowser extends Component { return { filters: { toponym: { - counter: 0, + counter: 0, state: true }, prison: { - counter: 0, + counter: 0, state: true } }, @@ -102,6 +102,9 @@ class MapBrowser extends Component { filter: feature => { let type = feature.properties.entityType return filters[type].state + }, + coordsToLatLng: coords => { + return new L.LatLng(coords[0], coords[1]) } }) @@ -128,7 +131,7 @@ class MapBrowser extends Component { let id = e.layer.feature.properties.entityId this._showLocation(id, e.layer) }) - this.markers.addLayer(this.geojson) + this.markers.addLayer(this.geojson) this.map.addLayer(this.markers) } diff --git a/packages/ost-publisher/OstPublisher.js b/packages/ost-publisher/OstPublisher.js index b4e7dca..199b235 100644 --- a/packages/ost-publisher/OstPublisher.js +++ b/packages/ost-publisher/OstPublisher.js @@ -1,14 +1,15 @@ // import { ContainerEditor, Highlights, Layout, ProseEditor, SplitPane, Toolbar } from 'substance' import { findIndex, forEach, map } from 'lodash-es' import OstPublisherContext from './OstPublisherContext' -import { Publisher } from 'archivist' +import { Publisher } from 'archivist-js' class OstPublisher extends Publisher { constructor(...args) { super(...args) this.handleActions({ - 'showTopics': this._showTopics + 'showTopics': this._showTopics, + 'resetBrackets': this._resetBrackets }) } @@ -39,6 +40,10 @@ class OstPublisher extends Publisher { }.bind(this)) } }) + + // If there is no collaborator data we should add it + let author = change.info.userId + if(author) this._addCollaborator(author) } // TODO: figure out why selection flags changed after comment update @@ -47,7 +52,6 @@ class OstPublisher extends Publisher { let doc = editorSession.getDocument() let contextPanel = this.refs.contextPanel - //let entityIndex = doc.getIndex('entities') let schema = doc.getSchema() let nodes = schema.nodeRegistry.entries let highlights = {} @@ -90,8 +94,14 @@ class OstPublisher extends Publisher { } } - this.contentHighlights.set(highlights) + let contextState = this.refs.contextPanel.getContextState() + if(contextState.contextId !== 'subjects' || contextState.mode !== 'edit') { + this._resetBrackets() + } else { + highlights['subject'] = this.contentHighlights._highlights.subject + } + this.contentHighlights.set(highlights) } } @@ -146,13 +156,24 @@ class OstPublisher extends Publisher { paragraphs = paragraphs.concat(paras) }) let firstPara = doc.getFirst(paragraphs) - this.refs.contentPanel.scrollTo(firstPara) + this.refs.contentPanel.scrollTo(`[data-id="${firstPara}"]`) setTimeout(function(){ this.refs.brackets.highlight(topics) this.highlightReferences(topics, true) }.bind(this), 10) } + + _resetBrackets(type) { + if(type) { + let highlights = {} + highlights[type] = [] + this.contentHighlights.set(highlights) + } + this.refs.brackets.resetBrackets() + let contextPanel = this.refs.contextPanel + contextPanel.resetSubjectsList() + } } export default OstPublisher diff --git a/packages/ost-publisher/OstPublisherContext.js b/packages/ost-publisher/OstPublisherContext.js index fb4cc86..b5c62e4 100644 --- a/packages/ost-publisher/OstPublisherContext.js +++ b/packages/ost-publisher/OstPublisherContext.js @@ -1,7 +1,7 @@ // import { Component } from 'substance' // import { forEach } from 'lodash-es' -import { PublisherContext } from 'archivist' +import { PublisherContext } from 'archivist-js' class OstPublisherContext extends PublisherContext { @@ -17,6 +17,13 @@ class OstPublisherContext extends PublisherContext { console.log('Edit container resource', node.id, ',', mode, 'mode') } + resetSubjectsList() { + let context = 'subjects' + if(this.refs[context]) { + this.refs[context].setSelected() + } + } + } export default OstPublisherContext \ No newline at end of file diff --git a/packages/ost-publisher/OstPublisherLayout.js b/packages/ost-publisher/OstPublisherLayout.js index fc51eb9..ca26fc3 100644 --- a/packages/ost-publisher/OstPublisherLayout.js +++ b/packages/ost-publisher/OstPublisherLayout.js @@ -1,7 +1,8 @@ -import { CollabSession, JSONConverter, series, substanceGlobals } from 'substance' -import { PublisherLayout } from 'archivist' +import { async, JSONConverter, substanceGlobals } from 'substance' +import { PublisherLayout, PublisherSession } from 'archivist-js' import OstPublisher from './OstPublisher' +const {series} = async let converter = new JSONConverter() class OstPublisherLayout extends PublisherLayout { @@ -24,10 +25,10 @@ class OstPublisherLayout extends PublisherLayout { return } - let document = configurator.createArticle() + let document = configurator.createDocument() let doc = converter.importDocument(document, docRecord.data) - let session = new CollabSession(doc, { + let session = new PublisherSession(doc, { configurator: configurator, documentId: documentId, version: docRecord.version, @@ -45,7 +46,8 @@ class OstPublisherLayout extends PublisherLayout { series([ this._loadResources(documentId, session), - this._loadSubjects(session) + this._loadSubjects(session), + this._loadCollaborators(documentId, session) ], () => { this.setState({ session: session diff --git a/packages/ost-publisher/_index.css b/packages/ost-publisher/_index.css index e69de29..6cc62d4 100644 --- a/packages/ost-publisher/_index.css +++ b/packages/ost-publisher/_index.css @@ -0,0 +1,3 @@ +.sc-publisher .se-context-section { + min-width: 480px; +} \ No newline at end of file diff --git a/packages/ost-publisher/package.js b/packages/ost-publisher/package.js index 85ab45f..48518fb 100644 --- a/packages/ost-publisher/package.js +++ b/packages/ost-publisher/package.js @@ -1,13 +1,67 @@ import OstPublisherLayout from './OstPublisherLayout' -import { BracketsPackage, TabbedContextPackage } from 'archivist' +import { BracketsPackage, CollaboratorsPackage, TabbedContextPackage } from 'archivist-js' +import { ContainerAnnotationPackage, FindAndReplacePackage } from 'substance' export default { name: 'ost-publisher', configure: function(config) { config.import(BracketsPackage) + config.import(CollaboratorsPackage) config.import(TabbedContextPackage) config.addComponent('editor', OstPublisherLayout) - config.addToolGroup('references') - config.addToolGroup('utils') + + config.import(ContainerAnnotationPackage) + config.import(FindAndReplacePackage, { + targetSurfaces: ['body'] + }) + + // Configure overlay + config.addToolPanel('main-overlay', [ + { + name: 'prompt', + type: 'tool-group', + commandGroups: ['prompt'] + } + ]) + + config.addToolPanel('workflow', [ + { + name: 'workflow', + type: 'tool-group', + commandGroups: ['workflows'] + } + ]) + + // Configure toolbar + config.addToolPanel('toolbar', [ + { + name: 'document', + type: 'tool-group', + showDisabled: true, + style: 'minimal', + commandGroups: ['undo-redo'] + }, + { + name: 'annotations', + type: 'tool-group', + showDisabled: true, + style: 'minimal', + commandGroups: ['annotations'] + }, + { + name: 'references', + type: 'tool-group', + showDisabled: true, + style: 'minimal', + commandGroups: ['references'] + }, + { + name: 'utils', + type: 'tool-group', + showDisabled: true, + style: 'minimal', + commandGroups: ['utils'] + } + ]) } } \ No newline at end of file diff --git a/packages/person-index/PersonIndex.js b/packages/person-index/PersonIndex.js index 18d478f..861b1ea 100644 --- a/packages/person-index/PersonIndex.js +++ b/packages/person-index/PersonIndex.js @@ -1,4 +1,4 @@ -import { Component, Grid, Layout, SplitPane } from 'substance' +import { Component, SplitPane } from 'substance' import { concat } from 'lodash-es' class PersonIndex extends Component { @@ -29,6 +29,7 @@ class PersonIndex extends Component { render($$) { let el = $$('div').addClass('sc-persons-index') let Header = this.getComponent('header') + let Spinner = this.getComponent('spinner') el.append($$(Header)) @@ -49,26 +50,15 @@ class PersonIndex extends Component { ) ) } else { - el.append( - $$('div').addClass('se-loader').append( - $$('div').addClass('se-spinner').append( - $$('div').addClass('se-rect1'), - $$('div').addClass('se-rect2'), - $$('div').addClass('se-rect3'), - $$('div').addClass('se-rect4'), - $$('div').addClass('se-rect5') - ), - $$('h2').html( - 'Loading...' - ) - ) - ) + el.append($$(Spinner, {message: 'spinner-loading'})) } return el } renderList($$) { + let Grid = this.getComponent('grid') + let items = this.state.items let total = this.state.total let PersonItem = this.getComponent('person-item') @@ -143,7 +133,7 @@ class PersonIndex extends Component { let perPage = this.state.perPage let options = { order: this.state.order + ' ' + this.state.direction, - limit: perPage, + limit: perPage, offset: pagination ? this.state.items.length : 0 } let items = [] @@ -211,4 +201,4 @@ class PersonIndex extends Component { } } -export default PersonIndex \ No newline at end of file +export default PersonIndex diff --git a/packages/person-index/PersonItem.js b/packages/person-index/PersonItem.js index f9d43bb..ef8ecd2 100644 --- a/packages/person-index/PersonItem.js +++ b/packages/person-index/PersonItem.js @@ -1,4 +1,4 @@ -import { Component, Grid } from 'substance' +import { Component } from 'substance' class PersonItem extends Component { diff --git a/packages/person-manager/PersonsPage.js b/packages/person-manager/PersonsPage.js index dfd1ce2..1265313 100644 --- a/packages/person-manager/PersonsPage.js +++ b/packages/person-manager/PersonsPage.js @@ -1,4 +1,4 @@ -import { AbstractEntityPage } from 'archivist' +import { AbstractEntityPage } from 'archivist-js' class PersonsPage extends AbstractEntityPage {} diff --git a/packages/person-manager/package.js b/packages/person-manager/package.js index 8491938..e75e7b6 100644 --- a/packages/person-manager/package.js +++ b/packages/person-manager/package.js @@ -4,5 +4,13 @@ export default { name: 'person-manager', configure: function(config) { config.addPage(PersonsPage.pageName, PersonsPage) + config.addLabel('persons', { + en: 'Persons', + ru: 'Персоналии' + }) + config.addLabel('add-person', { + en: '+ Add Person', + ru: '+ Добавить персоналию' + }) } } \ No newline at end of file diff --git a/packages/person/PersonContextItem.js b/packages/person/PersonContextItem.js index 7889a68..77b16b0 100644 --- a/packages/person/PersonContextItem.js +++ b/packages/person/PersonContextItem.js @@ -18,10 +18,6 @@ class PersonContextItem extends Component { .attr("data-id", this.props.entityId) .addClass('sc-entity-entry se-person') - if(this.props.mode !== 'view') { - el.on('click', this.handleEditorClick) - } - if(this.props.focus) { el.addClass('se-focused') } @@ -29,11 +25,16 @@ class PersonContextItem extends Component { el.append( $$('div').addClass('se-type').append(this.getLabel('person')), $$('div').addClass('se-title').append(node.name), - $$('div').addClass('se-description').setInnerHTML(node.description), - $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) - .on('click', this.editEntity) + $$('div').addClass('se-description').setInnerHTML(node.description) ) + if(this.props.mode !== 'view') { + el.append( + $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) + .on('click', this.editEntity) + ).on('click', this.handleEditorClick) + } + return el } diff --git a/packages/person/PersonReference.js b/packages/person/PersonReference.js index fe641ba..5cb5426 100644 --- a/packages/person/PersonReference.js +++ b/packages/person/PersonReference.js @@ -18,6 +18,14 @@ class PersonReference extends PropertyAnnotation { isResourceMultipleReference() { return false } + + setHighlighted(highlighted, scope) { + if (this.highlighted !== highlighted) { + this.highlightedScope = scope + this.highlighted = highlighted + this.emit('highlighted', highlighted) + } + } } PersonReference.define({ diff --git a/packages/person/PersonTool.js b/packages/person/PersonTool.js deleted file mode 100644 index 19e5e82..0000000 --- a/packages/person/PersonTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class PersonTool extends AnnotationTool {} - -export default PersonTool \ No newline at end of file diff --git a/packages/person/package.js b/packages/person/package.js index 069e6f6..41193a6 100644 --- a/packages/person/package.js +++ b/packages/person/package.js @@ -1,5 +1,4 @@ import PersonReference from './PersonReference' -import PersonTool from './PersonTool' import PersonComponent from './PersonComponent' import PersonCommand from './PersonCommand' import PersonContextItem from './PersonContextItem' @@ -8,7 +7,6 @@ export default { name: 'person', configure: function(config) { config.addNode(PersonReference) - //config.addTool(PersonReference.type, PersonTool, {toolGroup: 'references'}) config.addCommand(PersonReference.type, PersonCommand, { nodeType: PersonReference.type }) config.addIcon(PersonReference.type, {'fontawesome': 'fa-address-book-o'}) config.addComponent('person', PersonComponent) diff --git a/packages/prison-manager/PrisonsPage.js b/packages/prison-manager/PrisonsPage.js index dbe734a..9e76ab4 100644 --- a/packages/prison-manager/PrisonsPage.js +++ b/packages/prison-manager/PrisonsPage.js @@ -1,4 +1,4 @@ -import { AbstractEntityPage } from 'archivist' +import { AbstractEntityPage } from 'archivist-js' class PrisonsPage extends AbstractEntityPage {} diff --git a/packages/prison-manager/package.js b/packages/prison-manager/package.js index c875154..d7ac199 100644 --- a/packages/prison-manager/package.js +++ b/packages/prison-manager/package.js @@ -4,5 +4,13 @@ export default { name: 'prison-manager', configure: function(config) { config.addPage(PrisonsPage.pageName, PrisonsPage) + config.addLabel('Prisons', { + en: 'Prisons', + ru: 'Места заключения' + }) + config.addLabel('add-prison', { + en: '+ Add Prison', + ru: '+ Добавить место заключения' + }) } } \ No newline at end of file diff --git a/packages/prison/PrisonContextItem.js b/packages/prison/PrisonContextItem.js index f3ca732..96fd759 100644 --- a/packages/prison/PrisonContextItem.js +++ b/packages/prison/PrisonContextItem.js @@ -17,10 +17,6 @@ class PrisonContextItem extends Component { let el = $$('div') .attr("data-id", this.props.entityId) .addClass('sc-entity-entry se-prison') - - if(this.props.mode !== 'view') { - el.on('click', this.handleEditorClick) - } if(this.props.focus) { el.addClass('se-focused') @@ -29,11 +25,16 @@ class PrisonContextItem extends Component { el.append( $$('div').addClass('se-type').append(this.getLabel('prison')), $$('div').addClass('se-title').append(node.name), - $$('div').addClass('se-description').setInnerHTML(node.description), - $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) - .on('click', this.editEntity) + $$('div').addClass('se-description').setInnerHTML(node.description) ) + if(this.props.mode !== 'view') { + el.append( + $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) + .on('click', this.editEntity) + ).on('click', this.handleEditorClick) + } + return el } diff --git a/packages/prison/PrisonReference.js b/packages/prison/PrisonReference.js index 45c6ab8..99f601d 100644 --- a/packages/prison/PrisonReference.js +++ b/packages/prison/PrisonReference.js @@ -18,6 +18,14 @@ class PrisonReference extends PropertyAnnotation { isResourceMultipleReference() { return false } + + setHighlighted(highlighted, scope) { + if (this.highlighted !== highlighted) { + this.highlightedScope = scope + this.highlighted = highlighted + this.emit('highlighted', highlighted) + } + } } PrisonReference.define({ diff --git a/packages/prison/PrisonTool.js b/packages/prison/PrisonTool.js deleted file mode 100644 index 8926220..0000000 --- a/packages/prison/PrisonTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class PrisonTool extends AnnotationTool {} - -export default PrisonTool \ No newline at end of file diff --git a/packages/prison/_index.css b/packages/prison/_index.css index fbb31ad..35869e4 100644 --- a/packages/prison/_index.css +++ b/packages/prison/_index.css @@ -31,11 +31,15 @@ } .se-entity-entries .sc-entity-entry.se-prison .se-title { - margin-top: 30px; + margin-top: 0px; margin-bottom: 10px; margin-right: 60px; } +.sc-reader .se-entity-entries .sc-entity-entry.se-prison .se-title { + margin-top: 30px; +} + .sc-entity-entry.se-prison .se-prison-type { position: absolute; left: 20px; diff --git a/packages/prison/package.js b/packages/prison/package.js index 8736809..36a9a5e 100644 --- a/packages/prison/package.js +++ b/packages/prison/package.js @@ -1,5 +1,4 @@ import PrisonReference from './PrisonReference' -import PrisonTool from './PrisonTool' import PrisonComponent from './PrisonComponent' import PrisonCommand from './PrisonCommand' import PrisonContextItem from './PrisonContextItem' @@ -8,7 +7,6 @@ export default { name: 'prison', configure: function(config) { config.addNode(PrisonReference) - //config.addTool(PrisonReference.type, PrisonTool, {toolGroup: 'references'}) config.addCommand(PrisonReference.type, PrisonCommand, { nodeType: PrisonReference.type }) config.addIcon(PrisonReference.type, {'fontawesome': 'fa-th'}) config.addComponent('prison', PrisonComponent) diff --git a/packages/reader/Reader.js b/packages/reader/Reader.js index dfbcdd8..aef2bde 100644 --- a/packages/reader/Reader.js +++ b/packages/reader/Reader.js @@ -1,7 +1,10 @@ -import { ContainerEditor, Highlights, Layout, ProseEditor, SplitPane, TextPropertyEditor } from 'substance' -import { forEach, map, orderBy, uniq } from 'lodash-es' +import { ProseEditorPackage } from 'substance' +import { ContainerEditor, Highlights, Layout, SplitPane, TextPropertyEditor } from 'substance' +import { forEach, map, uniq } from 'lodash-es' import ReaderContext from './ReaderContext' +const { ProseEditor } = ProseEditorPackage + class Reader extends ProseEditor { constructor(...args) { super(...args) @@ -19,10 +22,12 @@ class Reader extends ProseEditor { let parent = this.getParent() let entityId = parent.props.entityId if(entityId) { - this._showReferences(entityId, true) + setTimeout(() => { + this._showReferences(entityId, true) + }, 10) } if(parent.props.fragment) { - this.refs.contentPanel.scrollTo(parent.props.fragment) + this.refs.contentPanel.scrollTo(`[data-id="${parent.props.fragment}"]`) } } @@ -81,13 +86,15 @@ class Reader extends ProseEditor { disabled: true }).addClass('se-title'), $$(ContainerEditor, { - disabled: 'true', + disabled: true, editorSession: this.editorSession, node: doc.get('body'), - commands: configurator.getSurfaceCommandNames(), - textTypes: configurator.getTextTypes() + commands: configurator.getSurfaceCommandNames() }).ref('body'), - $$(Overlay) + $$(Overlay, { + toolPanel: configurator.getToolPanel('main-overlay'), + theme: 'dark' + }) ) contentPanel.append(layout) @@ -134,12 +141,25 @@ class Reader extends ProseEditor { let doc = editorSession.getDocument() let entityIndex = doc.getIndex('entities') let refs = entityIndex.get(entityId) - let ordered = orderBy(refs, ref => { - let p = ref.path[0] - return container.getPosition(p) + // We are sorting references by paregraph position + // if nodes annotations are in same paragraph + // we will sort them by start offset + let refIds = Object.keys(refs) + let ordered = refIds.sort((a,b) => { + const refAPath = refs[a].getPath() + const refBPath = refs[b].getPath() + + if (refAPath[0] !== refBPath[0]){ + return (container.getPosition(refAPath[0]) - container.getPosition(refBPath[0])) + } else { + const refAOffset = refs[a].start.getOffset() + const refBOffset = refs[b].start.getOffset() + + return (refAOffset - refBOffset) + } }) - this.refs.contentPanel.scrollTo(ordered[0].id) + this.refs.contentPanel.scrollTo(`[data-id="${ordered[0]}"]`) this.highlightReferences([entityId]) if(!silent) { @@ -159,7 +179,7 @@ class Reader extends ProseEditor { paragraphs = paragraphs.concat(paras) }) let firstPara = doc.getFirst(paragraphs) - this.refs.contentPanel.scrollTo(firstPara) + this.refs.contentPanel.scrollTo(`[data-id="${firstPara}"]`) setTimeout(function(){ this.refs.brackets.highlight(topics) diff --git a/packages/reader/ReaderLayout.js b/packages/reader/ReaderLayout.js index d8c51af..07a3af3 100644 --- a/packages/reader/ReaderLayout.js +++ b/packages/reader/ReaderLayout.js @@ -1,6 +1,7 @@ -import { Component, EditorSession, JSONConverter, Layout, series } from 'substance' +import { async, Component, EditorSession, JSONConverter } from 'substance' import Reader from './Reader' +const {series} = async let converter = new JSONConverter() class ReaderLayout extends Component { @@ -35,9 +36,9 @@ class ReaderLayout extends Component { } if (newProps.entityId !== this.props.entityId && newProps.entityId !== undefined) { - setTimeout(function(){ + setTimeout(() => { this.refs.reader.highlightReferences([newProps.entityId]) - }.bind(this), 10) + }, 10) } } @@ -50,35 +51,20 @@ class ReaderLayout extends Component { } render($$) { + let Layout = this.getComponent('layout') + let Spinner = this.getComponent('spinner') + let el = $$('div').addClass('sc-read-document') let Header = this.getComponent('header') el.append($$(Header)) let main = $$(Layout, { width: 'medium', textAlign: 'center' - }).append( - $$('div').addClass('se-spinner').append( - $$('div').addClass('se-rect1'), - $$('div').addClass('se-rect2'), - $$('div').addClass('se-rect3'), - $$('div').addClass('se-rect4'), - $$('div').addClass('se-rect5') - ), - $$('h2').html( - 'Loading...' - ) - ) + }).append($$(Spinner, {message: 'spinner-loading'})) this._updateLayout() - if (this.state.error) { - main = $$('div').append( - $$(Notification, { - type: 'error', - message: this.state.error.message - }) - ) - } else if (this.state.session) { + if (this.state.session) { main = $$(Reader, { configurator: this.props.configurator, editorSession: this.state.session @@ -107,7 +93,7 @@ class ReaderLayout extends Component { return } //let docRecord = SampleDoc - let document = configurator.createArticle() + let document = configurator.createDocument() let doc = converter.importDocument(document, docRecord.data) let session = new EditorSession(doc, { diff --git a/packages/reader/_index.css b/packages/reader/_index.css index dc4e54a..e853ffd 100644 --- a/packages/reader/_index.css +++ b/packages/reader/_index.css @@ -19,7 +19,11 @@ white-space: pre-wrap; } -.sc-reader .se-context-section { +.sc-reader .sc-text-property .sc-comment { + background: none; +} + +.sc-reader .se-context-section { background: #fafafa; border-left: 1px solid var(--border-color); box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); diff --git a/packages/reader/package.js b/packages/reader/package.js index 4c045f8..46507fd 100644 --- a/packages/reader/package.js +++ b/packages/reader/package.js @@ -1,4 +1,5 @@ -import { BracketsPackage, TabbedContextPackage } from 'archivist' +import { ContainerAnnotationPackage } from 'substance' +import { BracketsPackage, TabbedContextPackage } from 'archivist-js' import ReaderLayout from './ReaderLayout' export default { @@ -7,6 +8,14 @@ export default { config.import(BracketsPackage) config.import(TabbedContextPackage) config.addComponent('reader', ReaderLayout) - config.addToolGroup('references') + + config.import(ContainerAnnotationPackage) + config.addToolPanel('main-overlay', [ + { + name: 'prompt', + type: 'tool-group', + commandGroups: ['prompt'] + } + ]) } } \ No newline at end of file diff --git a/packages/resources-context/ResourcesContext.js b/packages/resources-context/ResourcesContext.js index 7764f87..1a2dd7b 100644 --- a/packages/resources-context/ResourcesContext.js +++ b/packages/resources-context/ResourcesContext.js @@ -26,7 +26,7 @@ class ResourcesContext extends Component { didUpdate() { if(this.state.entityId && !this.state.noScroll) { - this.refs.panelEl.scrollTo(this.state.entityId) + this.refs.panelEl.scrollTo(`[data-id="${this.state.entityId}"]`) } } diff --git a/packages/scholar/Scholar.js b/packages/scholar/Scholar.js index 998d7fc..5346dcc 100644 --- a/packages/scholar/Scholar.js +++ b/packages/scholar/Scholar.js @@ -1,4 +1,4 @@ -import { AbstractApplication } from 'archivist' +import { AbstractApplication } from 'archivist-js' import { cloneDeep, forEach } from 'lodash-es' import ScholarRouter from './ScholarRouter' diff --git a/packages/scholar/package.js b/packages/scholar/package.js index c5cb201..da0d536 100644 --- a/packages/scholar/package.js +++ b/packages/scholar/package.js @@ -1,21 +1,13 @@ -import { DocumentPackage, PagerPackage, ToolboxPackage } from 'archivist' +import { BasePackage } from 'substance' +import { DocumentPackage, PagerPackage, SpinnerPackage, ToolboxPackage } from 'archivist-js' export default { name: 'scholar', configure: function(config) { + config.import(BasePackage) config.import(DocumentPackage) - - // config.import(Note); - // config.import(Dashboard); config.import(PagerPackage) + config.import(SpinnerPackage) config.import(ToolboxPackage) - // //config.import(LoaderPackage); - // config.import(NotificationPackage); - // config.import(CollaboratorsPackage); - - - // // Default configuration for available modes - // config.addConfigurator('reader', ReaderConfigurator); - // config.addConfigurator('writer', WriterConfigurator); } } \ No newline at end of file diff --git a/packages/server/Database.js b/packages/server/Database.js index 5680f90..d9be70d 100644 --- a/packages/server/Database.js +++ b/packages/server/Database.js @@ -6,7 +6,8 @@ let Promise = require('bluebird') Implements Database Conection API. */ class Database { - constructor() { + constructor(dbUrl) { + this.db_url = dbUrl || config.get('db_url') this.connect() } @@ -14,7 +15,6 @@ class Database { Connect to the db */ connect() { - this.db_url = config.get('db_url') if (!this.db_url) { throw new Error('Could not find db connection string') } diff --git a/packages/server/converter/ConverterServer.js b/packages/server/converter/ConverterServer.js index 89feb1d..29e8ad4 100644 --- a/packages/server/converter/ConverterServer.js +++ b/packages/server/converter/ConverterServer.js @@ -10,12 +10,12 @@ class ConverterServer { } bind(app) { - app.get(this.path + '/convert', this._convert.bind(this)) + //app.get(this.path + '/convert', this._convert.bind(this)) } _convert(req, res, next) { res.send('

Current data will be converted in couple of minutes...

') - let converter = spawn('../../convert.sh') + let converter = spawn('../convert.sh') converter.stdout.on('data', function(data) { console.log('stdout: ' + data); diff --git a/packages/server/document/DocumentServer.js b/packages/server/document/DocumentServer.js index 8f93d17..c959c36 100644 --- a/packages/server/document/DocumentServer.js +++ b/packages/server/document/DocumentServer.js @@ -1,4 +1,4 @@ -let ArchivistDocumentServer = require('archivist').DocumentServer +let ArchivistDocumentServer = require('archivist-js').DocumentServer let Promise = require('bluebird') let each = require('lodash/each') diff --git a/packages/server/package.js b/packages/server/package.js index 35b62ec..07edc34 100644 --- a/packages/server/package.js +++ b/packages/server/package.js @@ -7,14 +7,16 @@ let IndexerPackage = require('../indexer/package') let ResourceServerPackage = require('./resource/package') let ConverterServerPackage = require('./converter/package') let DocumentServerPackage = require('./document/package') -let AuthServerPackage = require('archivist').AuthServerPackage -let CollabServerPackage = require('archivist').CollabServerPackage -let UserServerPackage = require('archivist').UserServerPackage -let InspectorPackage = require('archivist').InspectorPackage +let ArchivistSubConfigurator = require('archivist-js').ArchivistSubConfigurator +let AuthServerPackage = require('archivist-js').AuthServerPackage +let CollabServerPackage = require('archivist-js').CollabServerPackage +let UserServerPackage = require('archivist-js').UserServerPackage +let InspectorPackage = require('archivist-js').InspectorPackage let db = new Database() let InterviewPackage = require('../../dist/ost.cjs').InterviewPackage +let SubjectsPackage = require('../../dist/ost.cjs').SubjectsPackage module.exports = { name: 'ost-server', @@ -31,5 +33,8 @@ module.exports = { config.import(ResourceServerPackage) config.import(UserServerPackage) config.import(ConverterServerPackage) + + // Subjects subconfigurator + config.addConfigurator('archivist-subjects', new ArchivistSubConfigurator().import(SubjectsPackage)) } } \ No newline at end of file diff --git a/packages/server/resource/ResourceServer.js b/packages/server/resource/ResourceServer.js index b49630c..f0a1c88 100644 --- a/packages/server/resource/ResourceServer.js +++ b/packages/server/resource/ResourceServer.js @@ -1,4 +1,4 @@ -let ArchivistResourceServer = require('archivist').ResourceServer +let ArchivistResourceServer = require('archivist-js').ResourceServer /* ResourceServer module. Can be bound to an express instance @@ -57,7 +57,7 @@ class ResourceServer extends ArchivistResourceServer { filters = filters ? JSON.parse(filters) : {} ///refs = refs ? JSON.parse(refs) : [] - + this.engine.getResourcesTreeFacets(filters, type) .then(function(entities) { res.json(entities) @@ -87,7 +87,7 @@ class ResourceServer extends ArchivistResourceServer { let letter = req.query.letter let options = req.query.options options = options ? JSON.parse(options) : {} - + this.engine.getPersonsList(letter, options) .then(function(persons) { res.json(persons) diff --git a/packages/subject-manager/SubjectsPage.js b/packages/subject-manager/SubjectsPage.js index 5a6e1fa..775db9e 100644 --- a/packages/subject-manager/SubjectsPage.js +++ b/packages/subject-manager/SubjectsPage.js @@ -1,10 +1,7 @@ -import { Button, Component, FontAwesomeIcon as Icon, Grid, Input, Layout, Modal, SplitPane, SubstanceError as Err } from 'substance' -import { concat, each, findIndex, flattenDeep, isEmpty, sortBy, map, throttle} from 'lodash-es' +import { Component, FontAwesomeIcon as Icon, SplitPane, SubstanceError as Err } from 'substance' +import { concat, each, flattenDeep, isEmpty, sortBy, map, throttle} from 'lodash-es' import moment from 'moment' -// Sample data for debugging -// import DataSample from '../../data/docs' - class SubjectsPage extends Component { constructor(...args) { super(...args) @@ -42,6 +39,8 @@ class SubjectsPage extends Component { } render($$) { + const Modal = this.getComponent('modal') + let items = this.state.items let el = $$('div').addClass('sc-subjects-page') let main = $$('div').addClass('se-entity-layout') @@ -98,6 +97,8 @@ class SubjectsPage extends Component { } renderFilters($$) { + const Input = this.getComponent('input') + let filters = [] let search = $$('div').addClass('se-search').append( $$(Icon, {icon: 'fa-search'}) @@ -145,6 +146,8 @@ class SubjectsPage extends Component { } renderEmpty($$) { + const Layout = this.getComponent('layout') + let layout = $$(Layout, { width: 'medium', textAlign: 'center' @@ -173,6 +176,8 @@ class SubjectsPage extends Component { } renderAdditionalMenu($$, actions) { + const Button = this.getComponent('button') + let el = $$('div').addClass('se-more').attr({'tabindex': 0}) let actionsList = $$('ul').addClass('se-more-content') each(actions, action => { @@ -188,7 +193,8 @@ class SubjectsPage extends Component { } renderFull($$) { - let urlHelper = this.context.urlHelper + const Grid = this.getComponent('grid') + let items = this.state.items let grid = $$(Grid) @@ -207,6 +213,8 @@ class SubjectsPage extends Component { } renderChildren($$, node, level) { + const Grid = this.getComponent('grid') + let items = this.state.items let edited = ['Updated', moment(node.edited).fromNow(), 'by', node.updatedBy].join(' ') let isHighlighted = node.selected @@ -234,7 +242,8 @@ class SubjectsPage extends Component { let additionalActions = [ {label: 'Edit', action: this._editItem.bind(this, node.id)}, - {label: 'Documents', action: this._loadReferences.bind(this, node.id)} + {label: 'Documents', action: this._loadReferences.bind(this, node.id)}, + {label: 'Add Child', action: this._newSubject.bind(this, node.id)} ] if(hideExpand) { @@ -457,11 +466,13 @@ class SubjectsPage extends Component { /* Create a new subject */ - _newSubject() { + _newSubject(parent) { + parent = (parent && typeof parent === 'string') ? parent : 'root' let authenticationClient = this.context.authenticationClient let user = authenticationClient.getUser() let resourceClient = this.context.resourceClient let items = this.state.items + let position = parent === 'root' ? Object.keys(items.getRoots()).length : items.getChildren(parent).length let entityData = { name: 'New subject', synonyms: [], @@ -472,8 +483,8 @@ class SubjectsPage extends Component { data: { name: 'New subject', workname: '', - parent: 'root', - position: Object.keys(items.getRoots()).length, + parent: parent, + position: position, description: '' } } @@ -497,7 +508,7 @@ class SubjectsPage extends Component { position: entity.data.position, count: 0, description: entity.data.description, - parent: 'root' + parent: parent }) this.extendProps({ diff --git a/packages/subject-manager/package.js b/packages/subject-manager/package.js index c33f7b9..d81013e 100644 --- a/packages/subject-manager/package.js +++ b/packages/subject-manager/package.js @@ -4,9 +4,12 @@ export default { name: 'subject-manager', configure: function(config) { config.addPage(SubjectsPage.pageName, SubjectsPage) - config.addIcon('collapsed', { 'fontawesome': 'fa-caret-right' }) config.addIcon('expanded', { 'fontawesome': 'fa-caret-down' }) config.addIcon('dnd', { 'fontawesome': 'fa-arrows' }) + config.addLabel('subjects', { + en: 'Subjects', + ru: 'Темы' + }) } } \ No newline at end of file diff --git a/packages/subject/SubjectCommand.js b/packages/subject/SubjectCommand.js index de081f7..f6e5c21 100644 --- a/packages/subject/SubjectCommand.js +++ b/packages/subject/SubjectCommand.js @@ -1,4 +1,6 @@ -import { ContainerAnnotationCommand } from 'substance' +import { ContainerAnnotationPackage } from 'substance' + +const { ContainerAnnotationCommand } = ContainerAnnotationPackage class SubjectCommand extends ContainerAnnotationCommand { diff --git a/packages/subject/SubjectReference.js b/packages/subject/SubjectReference.js index 6fe1eb4..335ce7b 100644 --- a/packages/subject/SubjectReference.js +++ b/packages/subject/SubjectReference.js @@ -1,4 +1,5 @@ import { ContainerAnnotation } from 'substance' +import { forEach } from 'lodash-es' /** SubjectReference Node. @@ -32,6 +33,17 @@ class SubjectReference extends ContainerAnnotation { isResourceReference() { return false } + + setHighlighted(highlighted, scope) { + if (this.highlighted !== highlighted) { + this.highlighted = highlighted + this.highlightedScope = scope + this.emit('highlighted', highlighted, scope) + forEach(this.fragments, function(frag) { + frag.emit('highlighted', highlighted, scope) + }) + } + } } SubjectReference.define({ diff --git a/packages/subject/SubjectTool.js b/packages/subject/SubjectTool.js deleted file mode 100644 index 3eb2148..0000000 --- a/packages/subject/SubjectTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class SubjectTool extends AnnotationTool {} - -export default SubjectTool diff --git a/packages/subject/package.js b/packages/subject/package.js index ffb5b90..c7f384e 100644 --- a/packages/subject/package.js +++ b/packages/subject/package.js @@ -1,5 +1,4 @@ import SubjectReference from './SubjectReference' -import SubjectTool from './SubjectTool' import SubjectCommand from './SubjectCommand' import SubjectComponent from './SubjectComponent' @@ -7,9 +6,13 @@ export default { name: 'subject', configure: function(config) { config.addNode(SubjectReference) - config.addTool(SubjectReference.type, SubjectTool, { toolGroup: 'references' }) - config.addCommand(SubjectReference.type, SubjectCommand, { nodeType: SubjectReference.type }) - config.addComponent('container-annotation-fragment', SubjectComponent) + config.addCommand(SubjectReference.type, SubjectCommand, { nodeType: SubjectReference.type, commandGroup: 'references' }) + config.addComponent(SubjectReference.type, SubjectComponent) config.addIcon(SubjectReference.type, {'fontawesome': 'fa-tags'}) + + config.addLabel('subject', { + en: 'subject reference', + ru: 'связать с рубрикой' + }) } } \ No newline at end of file diff --git a/packages/subjects-editor-context/SubjectsContext.js b/packages/subjects-editor-context/SubjectsContext.js index 94ee7f0..3affbfe 100644 --- a/packages/subjects-editor-context/SubjectsContext.js +++ b/packages/subjects-editor-context/SubjectsContext.js @@ -35,8 +35,6 @@ class SubjectsContext extends Component { return this.renderList($$) } else if (mode === 'edit') { return this.renderSubjectSelector($$) - } else { - //return this.renderItem($$) } } @@ -88,11 +86,13 @@ class SubjectsContext extends Component { } highlightNodes(activeNode) { - let subjects = this.state.subjects + let editorSession = this.context.editorSession + let subjects = editorSession.subjects subjects.resetSelection() let activeNodes = subjects.getAllChildren(activeNode) activeNodes.unshift(activeNode) this.send('showTopics', activeNodes) + this.setSelected(activeNode) } renderChildren($$, node, level) { @@ -114,7 +114,7 @@ class SubjectsContext extends Component { .ref(node.id) .on('click', this.highlightNodes.bind(this, node.id)) - if(node.id === this.props.topic) { + if(this.state.selected === node.id) { el.addClass('sm-active') } @@ -124,10 +124,18 @@ class SubjectsContext extends Component { } } + setSelected(node) { + this.extendState({selected: node}) + // let editorSession = this.context.editorSession + // let subjects = editorSession.subjects + // subjects.set([node, 'selected'], true) + // this.rerender() + } + _showList() { - this.extendProps({ - mode: 'list' - }) + this.send('resetBrackets', 'subject') + this.send('switchContext', {mode: 'list'}) + //this.extendState({selected: undefined}) } } diff --git a/packages/subjects/SubjectsImporter.js b/packages/subjects/SubjectsImporter.js index 153fd53..28c7d8b 100644 --- a/packages/subjects/SubjectsImporter.js +++ b/packages/subjects/SubjectsImporter.js @@ -8,7 +8,7 @@ class SubjectsImporter extends HTMLImporter { importDocument(subjectsData, reader, facets) { this.reset() - let doc = this.generateDocument() + let doc = this.state.doc each(subjectsData, function(subject) { if(reader) { doc.create({ diff --git a/packages/subjects/SubjectsIndex.js b/packages/subjects/SubjectsIndex.js index 0b9c73d..4481df1 100644 --- a/packages/subjects/SubjectsIndex.js +++ b/packages/subjects/SubjectsIndex.js @@ -61,7 +61,7 @@ class SubjectIndex extends NodeIndex { return new SubjectIndex(this.doc) } - _clear() { + clear() { this.index.clear() } } diff --git a/packages/subjects/package.js b/packages/subjects/package.js index 1101670..ccee7d2 100644 --- a/packages/subjects/package.js +++ b/packages/subjects/package.js @@ -8,8 +8,9 @@ export default { configure: function(config) { config.defineSchema({ name: 'archivist-subjects', - ArticleClass: Subjects, - defaultTextType: 'paragraph' + version: '1.0.0', + DocumentClass: Subjects, + defaultTextType: 'subject' }) config.addNode(Subject) diff --git a/packages/toponym-manager/ToponymsPage.js b/packages/toponym-manager/ToponymsPage.js index 656ef0c..8972895 100644 --- a/packages/toponym-manager/ToponymsPage.js +++ b/packages/toponym-manager/ToponymsPage.js @@ -1,4 +1,4 @@ -import { AbstractEntityPage } from 'archivist' +import { AbstractEntityPage } from 'archivist-js' class ToponymsPage extends AbstractEntityPage {} diff --git a/packages/toponym-manager/package.js b/packages/toponym-manager/package.js index 6af38a2..7dd196c 100644 --- a/packages/toponym-manager/package.js +++ b/packages/toponym-manager/package.js @@ -4,5 +4,13 @@ export default { name: 'toponym-manager', configure: function(config) { config.addPage(ToponymsPage.pageName, ToponymsPage) + config.addLabel('toponyms', { + en: 'Toponyms', + ru: 'Топонимы' + }) + config.addLabel('add-toponym', { + en: '+ Add Toponym', + ru: '+ Добавить топоним' + }) } } \ No newline at end of file diff --git a/packages/toponym/ToponymContextItem.js b/packages/toponym/ToponymContextItem.js index 24d2689..6482344 100644 --- a/packages/toponym/ToponymContextItem.js +++ b/packages/toponym/ToponymContextItem.js @@ -17,10 +17,6 @@ class ToponymContextItem extends Component { let el = $$('div') .attr("data-id", this.props.entityId) .addClass('sc-entity-entry se-toponym') - - if(this.props.mode !== 'view') { - el.on('click', this.handleEditorClick) - } if(this.props.focus) { el.addClass('se-focused') @@ -29,11 +25,16 @@ class ToponymContextItem extends Component { el.append( $$('div').addClass('se-type').append(this.getLabel('toponym')), $$('div').addClass('se-title').append(node.name), - $$('div').addClass('se-description').setInnerHTML(node.description), - $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) - .on('click', this.editEntity) + $$('div').addClass('se-description').setInnerHTML(node.description) ) + if(this.props.mode !== 'view') { + el.append( + $$('div').addClass('se-edit-entity').append(this.context.iconProvider.renderIcon($$, 'editEntity')) + .on('click', this.editEntity) + ).on('click', this.handleEditorClick) + } + return el } diff --git a/packages/toponym/ToponymReference.js b/packages/toponym/ToponymReference.js index adab827..bf90c44 100644 --- a/packages/toponym/ToponymReference.js +++ b/packages/toponym/ToponymReference.js @@ -18,6 +18,14 @@ class ToponymReference extends PropertyAnnotation { isResourceMultipleReference() { return false } + + setHighlighted(highlighted, scope) { + if (this.highlighted !== highlighted) { + this.highlightedScope = scope + this.highlighted = highlighted + this.emit('highlighted', highlighted) + } + } } ToponymReference.define({ diff --git a/packages/toponym/ToponymTool.js b/packages/toponym/ToponymTool.js deleted file mode 100644 index 8037821..0000000 --- a/packages/toponym/ToponymTool.js +++ /dev/null @@ -1,5 +0,0 @@ -import { AnnotationTool } from 'substance' - -class ToponymTool extends AnnotationTool {} - -export default ToponymTool \ No newline at end of file diff --git a/packages/toponym/package.js b/packages/toponym/package.js index 7eff59e..abb11e6 100644 --- a/packages/toponym/package.js +++ b/packages/toponym/package.js @@ -1,5 +1,4 @@ import ToponymReference from './ToponymReference' -import ToponymTool from './ToponymTool' import ToponymComponent from './ToponymComponent' import ToponymCommand from './ToponymCommand' import ToponymContextItem from './ToponymContextItem' @@ -8,7 +7,6 @@ export default { name: 'toponym', configure: function(config) { config.addNode(ToponymReference) - //config.addTool(ToponymReference.type, ToponymTool, {toolGroup: 'references'}) config.addCommand(ToponymReference.type, ToponymCommand, { nodeType: ToponymReference.type }) config.addIcon(ToponymReference.type, {'fontawesome': 'fa-globe'}) config.addComponent('toponym', ToponymComponent) diff --git a/packages/waypoint/Waypoint.js b/packages/waypoint/Waypoint.js new file mode 100644 index 0000000..a8db4bb --- /dev/null +++ b/packages/waypoint/Waypoint.js @@ -0,0 +1,21 @@ +import { DocumentNode } from 'substance' + +/* + Waypoint meta node. + Holds waypoint reference data. + + Attributes + - entityId ID of referenced entity + - density Density of waypoint + +*/ +class Waypoint extends DocumentNode {} + +Waypoint.type = 'waypoint' + +Waypoint.define({ + entityId: { type: 'string', default: ''}, + density: { type: 'string', default: '0'} +}) + +export default Waypoint diff --git a/packages/waypoint/package.js b/packages/waypoint/package.js new file mode 100644 index 0000000..823b54d --- /dev/null +++ b/packages/waypoint/package.js @@ -0,0 +1,8 @@ +import Waypoint from './Waypoint' + +export default { + name: 'waypoint', + configure: function(config) { + config.addNode(Waypoint) + } +} diff --git a/seed.js b/seed.js index 84577e8..eef50f5 100644 --- a/seed.js +++ b/seed.js @@ -1,8 +1,8 @@ 'use strict'; var Database = require('./packages/server/Database'); -var Configurator = require('archivist').ServerConfigurator; -var StorePackage = require('archivist').ArchivistStorePackage; +var Configurator = require('archivist-js').ServerConfigurator; +var StorePackage = require('archivist-js').ArchivistStorePackage; var db = new Database(); var configurator = new Configurator().import(StorePackage); diff --git a/server.js b/server.js index 9fdb19e..76bedb2 100644 --- a/server.js +++ b/server.js @@ -19,7 +19,7 @@ app.use(bodyParser.urlencoded({ extended: true, limit: '3mb', parameterLimit: 30 /* Config */ -var ServerConfigurator = require('archivist').ServerConfigurator; +var ServerConfigurator = require('archivist-js').ServerConfigurator; var ServerPackage = require('./packages/server/package'); var configurator = new ServerConfigurator(); configurator.setServerApp(app); @@ -31,10 +31,7 @@ configurator.setDefaultLanguage('russian'); /* Serve app */ -app.use('/archivist', express.static(path.join(__dirname, '/dist/archivist'))); -app.use('/font-awesome', express.static(path.join(__dirname, '/dist/font-awesome'))); -app.use('/markercluster', express.static(path.join(__dirname, '/dist/markercluster'))); -app.use('/substance', express.static(path.join(__dirname, '/dist/substance'))); +app.use('/libs', express.static(path.join(__dirname, '/dist/libs'))); if(config.publisherEndpoint) app.use(config.publisherEndpoint, express.static(path.join(__dirname, '/dist/publisher'))); if(config.scholarEndpoint) app.use(config.scholarEndpoint, express.static(path.join(__dirname, '/dist/scholar'))); app.use('/documents/:id', express.static(path.join(__dirname, '/dist/scholar')));