rename shaca entities to have F-prefix, #3476

This commit is contained in:
zadam 2023-01-03 13:40:21 +01:00
parent 977a47bc27
commit da161c7ce0
8 changed files with 77 additions and 76 deletions

24
package-lock.json generated
View File

@ -1,12 +1,11 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.58.0-beta", "version": "0.58.2-beta",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium", "version": "0.58.2-beta",
"version": "0.58.0-beta",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
@ -14,7 +13,7 @@
"@excalidraw/excalidraw": "0.13.0", "@excalidraw/excalidraw": "0.13.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"axios": "1.2.1", "axios": "1.2.2",
"better-sqlite3": "7.4.5", "better-sqlite3": "7.4.5",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"cls-hooked": "4.2.2", "cls-hooked": "4.2.2",
@ -29,6 +28,7 @@
"electron-debug": "3.2.0", "electron-debug": "3.2.0",
"electron-dl": "3.5.0", "electron-dl": "3.5.0",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"escape-html": "^1.0.3",
"express": "4.18.2", "express": "4.18.2",
"express-partial-content": "1.0.2", "express-partial-content": "1.0.2",
"express-rate-limit": "6.7.0", "express-rate-limit": "6.7.0",
@ -1877,9 +1877,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -4711,7 +4711,7 @@
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
@ -11984,9 +11984,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
}, },
"axios": { "axios": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"requires": { "requires": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -14166,7 +14166,7 @@
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
}, },
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",

View File

@ -48,6 +48,7 @@
"electron-debug": "3.2.0", "electron-debug": "3.2.0",
"electron-dl": "3.5.0", "electron-dl": "3.5.0",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"escape-html": "^1.0.3",
"express": "4.18.2", "express": "4.18.2",
"express-partial-content": "1.0.2", "express-partial-content": "1.0.2",
"express-rate-limit": "6.7.0", "express-rate-limit": "6.7.0",

View File

@ -1,6 +1,6 @@
let shaca; let shaca;
class AbstractEntity { class AbstractShacaEntity {
get shaca() { get shaca() {
if (!shaca) { if (!shaca) {
shaca = require("../shaca"); shaca = require("../shaca");
@ -10,4 +10,4 @@ class AbstractEntity {
} }
} }
module.exports = AbstractEntity; module.exports = AbstractShacaEntity;

View File

@ -1,8 +1,8 @@
"use strict"; "use strict";
const AbstractEntity = require('./abstract_entity'); const AbstractShacaEntity = require('./abstract_shaca_entity.js');
class Attribute extends AbstractEntity { class SAttribute extends AbstractShacaEntity {
constructor([attributeId, noteId, type, name, value, isInheritable, position]) { constructor([attributeId, noteId, type, name, value, isInheritable, position]) {
super(); super();
@ -69,24 +69,24 @@ class Attribute extends AbstractEntity {
return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name);
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
get note() { get note() {
return this.shaca.notes[this.noteId]; return this.shaca.notes[this.noteId];
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
get targetNote() { get targetNote() {
if (this.type === 'relation') { if (this.type === 'relation') {
return this.shaca.notes[this.value]; return this.shaca.notes[this.value];
} }
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
getNote() { getNote() {
return this.shaca.getNote(this.noteId); return this.shaca.getNote(this.noteId);
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
getTargetNote() { getTargetNote() {
if (this.type !== 'relation') { if (this.type !== 'relation') {
throw new Error(`Attribute ${this.attributeId} is not relation`); throw new Error(`Attribute ${this.attributeId} is not relation`);
@ -112,4 +112,4 @@ class Attribute extends AbstractEntity {
} }
} }
module.exports = Attribute; module.exports = SAttribute;

View File

@ -1,8 +1,8 @@
"use strict"; "use strict";
const AbstractEntity = require('./abstract_entity'); const AbstractShacaEntity = require('./abstract_shaca_entity.js');
class Branch extends AbstractEntity { class SBranch extends AbstractShacaEntity {
constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) { constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) {
super(); super();
@ -38,20 +38,20 @@ class Branch extends AbstractEntity {
this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
} }
/** @return {Note} */ /** @return {SNote} */
get childNote() { get childNote() {
return this.shaca.notes[this.noteId]; return this.shaca.notes[this.noteId];
} }
/** @return {Note} */ /** @return {SNote} */
getNote() { getNote() {
return this.childNote; return this.childNote;
} }
/** @return {Note} */ /** @return {SNote} */
get parentNote() { get parentNote() {
return this.shaca.notes[this.parentNoteId]; return this.shaca.notes[this.parentNoteId];
} }
} }
module.exports = Branch; module.exports = SBranch;

View File

@ -2,7 +2,7 @@
const sql = require('../../sql'); const sql = require('../../sql');
const utils = require('../../../services/utils'); const utils = require('../../../services/utils');
const AbstractEntity = require('./abstract_entity'); const AbstractShacaEntity = require('./abstract_shaca_entity.js');
const escape = require('escape-html'); const escape = require('escape-html');
const LABEL = 'label'; const LABEL = 'label';
@ -11,7 +11,7 @@ const CREDENTIALS = 'shareCredentials';
const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS; const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS;
class Note extends AbstractEntity { class SNote extends AbstractShacaEntity {
constructor([noteId, title, type, mime, utcDateModified, isProtected]) { constructor([noteId, title, type, mime, utcDateModified, isProtected]) {
super(); super();
@ -28,52 +28,52 @@ class Note extends AbstractEntity {
/** @param {boolean} */ /** @param {boolean} */
this.isProtected = isProtected; this.isProtected = isProtected;
/** @param {Branch[]} */ /** @param {SBranch[]} */
this.parentBranches = []; this.parentBranches = [];
/** @param {Note[]} */ /** @param {SNote[]} */
this.parents = []; this.parents = [];
/** @param {Note[]} */ /** @param {SNote[]} */
this.children = []; this.children = [];
/** @param {Attribute[]} */ /** @param {SAttribute[]} */
this.ownedAttributes = []; this.ownedAttributes = [];
/** @param {Attribute[]|null} */ /** @param {SAttribute[]|null} */
this.__attributeCache = null; this.__attributeCache = null;
/** @param {Attribute[]|null} */ /** @param {SAttribute[]|null} */
this.inheritableAttributeCache = null; this.inheritableAttributeCache = null;
/** @param {Attribute[]} */ /** @param {SAttribute[]} */
this.targetRelations = []; this.targetRelations = [];
this.shaca.notes[this.noteId] = this; this.shaca.notes[this.noteId] = this;
} }
/** @returns {Branch[]} */ /** @returns {SBranch[]} */
getParentBranches() { getParentBranches() {
return this.parentBranches; return this.parentBranches;
} }
/** @returns {Branch[]} */ /** @returns {SBranch[]} */
getBranches() { getBranches() {
return this.parentBranches; return this.parentBranches;
} }
/** @returns {Branch[]} */ /** @returns {SBranch[]} */
getChildBranches() { getChildBranches() {
return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId));
} }
/** @returns {Note[]} */ /** @returns {SNote[]} */
getParentNotes() { getParentNotes() {
return this.parents; return this.parents;
} }
/** @returns {Note[]} */ /** @returns {SNote[]} */
getChildNotes() { getChildNotes() {
return this.children; return this.children;
} }
/** @returns {Note[]} */ /** @returns {SNote[]} */
getVisibleChildNotes() { getVisibleChildNotes() {
return this.getChildBranches() return this.getChildBranches()
.filter(branch => !branch.isHidden) .filter(branch => !branch.isHidden)
@ -123,7 +123,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [type] - (optional) attribute type to filter * @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter * @param {string} [name] - (optional) attribute name to filter
* @returns {Attribute[]} all note's attributes, including inherited ones * @returns {SAttribute[]} all note's attributes, including inherited ones
*/ */
getAttributes(type, name) { getAttributes(type, name) {
this.__getAttributes([]); this.__getAttributes([]);
@ -142,7 +142,7 @@ class Note extends AbstractEntity {
} }
} }
/** @returns {Attribute[]} */ /** @returns {SAttribute[]} */
getCredentials() { getCredentials() {
this.__getAttributes([]); this.__getAttributes([]);
@ -200,7 +200,7 @@ class Note extends AbstractEntity {
return this.__attributeCache; return this.__attributeCache;
} }
/** @return {Attribute[]} */ /** @return {SAttribute[]} */
__getInheritableAttributes(path) { __getInheritableAttributes(path) {
if (path.includes(this.noteId)) { if (path.includes(this.noteId)) {
return []; return [];
@ -218,7 +218,7 @@ class Note extends AbstractEntity {
return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); return !!this.getAttributes().find(attr => attr.type === type && attr.name === name);
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
getRelationTarget(name) { getRelationTarget(name) {
const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name);
@ -251,25 +251,25 @@ class Note extends AbstractEntity {
/** /**
* @param {string} name - label name * @param {string} name - label name
* @returns {Attribute|null} label if it exists, null otherwise * @returns {SAttribute|null} label if it exists, null otherwise
*/ */
getLabel(name) { return this.getAttribute(LABEL, name); } getLabel(name) { return this.getAttribute(LABEL, name); }
/** /**
* @param {string} name - label name * @param {string} name - label name
* @returns {Attribute|null} label if it exists, null otherwise * @returns {SAttribute|null} label if it exists, null otherwise
*/ */
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); } getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
/** /**
* @param {string} name - relation name * @param {string} name - relation name
* @returns {Attribute|null} relation if it exists, null otherwise * @returns {SAttribute|null} relation if it exists, null otherwise
*/ */
getRelation(name) { return this.getAttribute(RELATION, name); } getRelation(name) { return this.getAttribute(RELATION, name); }
/** /**
* @param {string} name - relation name * @param {string} name - relation name
* @returns {Attribute|null} relation if it exists, null otherwise * @returns {SAttribute|null} relation if it exists, null otherwise
*/ */
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); } getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
@ -309,7 +309,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} type - attribute type (label, relation, etc.) * @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name * @param {string} name - attribute name
* @returns {Attribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note. * @returns {SAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
*/ */
getAttribute(type, name) { getAttribute(type, name) {
const attributes = this.getAttributes(); const attributes = this.getAttributes();
@ -341,7 +341,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [name] - label name to filter * @param {string} [name] - label name to filter
* @returns {Attribute[]} all note's labels (attributes with type label), including inherited ones * @returns {SAttribute[]} all note's labels (attributes with type label), including inherited ones
*/ */
getLabels(name) { getLabels(name) {
return this.getAttributes(LABEL, name); return this.getAttributes(LABEL, name);
@ -357,7 +357,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [name] - label name to filter * @param {string} [name] - label name to filter
* @returns {Attribute[]} all note's labels (attributes with type label), excluding inherited ones * @returns {SAttribute[]} all note's labels (attributes with type label), excluding inherited ones
*/ */
getOwnedLabels(name) { getOwnedLabels(name) {
return this.getOwnedAttributes(LABEL, name); return this.getOwnedAttributes(LABEL, name);
@ -373,7 +373,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [name] - relation name to filter * @param {string} [name] - relation name to filter
* @returns {Attribute[]} all note's relations (attributes with type relation), including inherited ones * @returns {SAttribute[]} all note's relations (attributes with type relation), including inherited ones
*/ */
getRelations(name) { getRelations(name) {
return this.getAttributes(RELATION, name); return this.getAttributes(RELATION, name);
@ -381,7 +381,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [name] - relation name to filter * @param {string} [name] - relation name to filter
* @returns {Attribute[]} all note's relations (attributes with type relation), excluding inherited ones * @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones
*/ */
getOwnedRelations(name) { getOwnedRelations(name) {
return this.getOwnedAttributes(RELATION, name); return this.getOwnedAttributes(RELATION, name);
@ -390,7 +390,7 @@ class Note extends AbstractEntity {
/** /**
* @param {string} [type] - (optional) attribute type to filter * @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter * @param {string} [name] - (optional) attribute name to filter
* @returns {Attribute[]} note's "owned" attributes - excluding inherited ones * @returns {SAttribute[]} note's "owned" attributes - excluding inherited ones
*/ */
getOwnedAttributes(type, name) { getOwnedAttributes(type, name) {
// it's a common mistake to include # or ~ into attribute name // it's a common mistake to include # or ~ into attribute name
@ -413,7 +413,7 @@ class Note extends AbstractEntity {
} }
/** /**
* @returns {Attribute} attribute belonging to this specific note (excludes inherited attributes) * @returns {SAttribute} attribute belonging to this specific note (excludes inherited attributes)
* *
* This method can be significantly faster than the getAttribute() * This method can be significantly faster than the getAttribute()
*/ */
@ -438,7 +438,7 @@ class Note extends AbstractEntity {
return !!this.targetRelations.find(rel => rel.name === 'template'); return !!this.targetRelations.find(rel => rel.name === 'template');
} }
/** @returns {Attribute[]} */ /** @returns {SAttribute[]} */
getTargetRelations() { getTargetRelations() {
return this.targetRelations; return this.targetRelations;
} }
@ -476,4 +476,4 @@ class Note extends AbstractEntity {
} }
} }
module.exports = Note; module.exports = SNote;

View File

@ -6,18 +6,18 @@ class Shaca {
} }
reset() { reset() {
/** @type {Object.<String, Note>} */ /** @type {Object.<String, SNote>} */
this.notes = {}; this.notes = {};
/** @type {Object.<String, Branch>} */ /** @type {Object.<String, SBranch>} */
this.branches = {}; this.branches = {};
/** @type {Object.<String, Branch>} */ /** @type {Object.<String, SBranch>} */
this.childParentToBranch = {}; this.childParentToBranch = {};
/** @type {Object.<String, Attribute>} */ /** @type {Object.<String, SAttribute>} */
this.attributes = {}; this.attributes = {};
/** @type {Object.<String, String>} */ /** @type {Object.<String, String>} */
this.aliasToNote = {}; this.aliasToNote = {};
/** @type {Note|null} */ /** @type {SNote|null} */
this.shareRootNote = null; this.shareRootNote = null;
/** @type {boolean} true if the index of all shared subtrees is enabled */ /** @type {boolean} true if the index of all shared subtrees is enabled */
@ -26,7 +26,7 @@ class Shaca {
this.loaded = false; this.loaded = false;
} }
/** @returns {Note|null} */ /** @returns {SNote|null} */
getNote(noteId) { getNote(noteId) {
return this.notes[noteId]; return this.notes[noteId];
} }
@ -36,7 +36,7 @@ class Shaca {
return noteId in this.notes; return noteId in this.notes;
} }
/** @returns {Note[]} */ /** @returns {SNote[]} */
getNotes(noteIds, ignoreMissing = false) { getNotes(noteIds, ignoreMissing = false) {
const filteredNotes = []; const filteredNotes = [];
@ -48,7 +48,7 @@ class Shaca {
continue; continue;
} }
throw new Error(`Note '${noteId}' was not found in becca.`); throw new Error(`Note '${noteId}' was not found in shaca.`);
} }
filteredNotes.push(note); filteredNotes.push(note);
@ -57,17 +57,17 @@ class Shaca {
return filteredNotes; return filteredNotes;
} }
/** @returns {Branch|null} */ /** @returns {SBranch|null} */
getBranch(branchId) { getBranch(branchId) {
return this.branches[branchId]; return this.branches[branchId];
} }
/** @returns {Branch|null} */ /** @returns {SBranch|null} */
getBranchFromChildAndParent(childNoteId, parentNoteId) { getBranchFromChildAndParent(childNoteId, parentNoteId) {
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
} }
/** @returns {Attribute|null} */ /** @returns {SAttribute|null} */
getAttribute(attributeId) { getAttribute(attributeId) {
return this.attributes[attributeId]; return this.attributes[attributeId];
} }

View File

@ -3,9 +3,9 @@
const sql = require('../sql'); const sql = require('../sql');
const shaca = require('./shaca'); const shaca = require('./shaca');
const log = require('../../services/log'); const log = require('../../services/log');
const Note = require('./entities/note'); const SNote = require('./entities/snote.js');
const Branch = require('./entities/branch'); const SBranch = require('./entities/sbranch.js');
const Attribute = require('./entities/attribute'); const SAttribute = require('./entities/sattribute.js');
const shareRoot = require('../share_root'); const shareRoot = require('../share_root');
const eventService = require("../../services/events"); const eventService = require("../../services/events");
@ -41,7 +41,7 @@ function load() {
AND noteId IN (${noteIdStr})`); AND noteId IN (${noteIdStr})`);
for (const row of rawNoteRows) { for (const row of rawNoteRows) {
new Note(row); new SNote(row);
} }
const rawBranchRows = sql.getRawRows(` const rawBranchRows = sql.getRawRows(`
@ -52,7 +52,7 @@ function load() {
ORDER BY notePosition`); ORDER BY notePosition`);
for (const row of rawBranchRows) { for (const row of rawBranchRows) {
new Branch(row); new SBranch(row);
} }
const rawAttributeRows = sql.getRawRows(` const rawAttributeRows = sql.getRawRows(`
@ -62,7 +62,7 @@ function load() {
AND noteId IN (${noteIdStr})`); AND noteId IN (${noteIdStr})`);
for (const row of rawAttributeRows) { for (const row of rawAttributeRows) {
new Attribute(row); new SAttribute(row);
} }
shaca.loaded = true; shaca.loaded = true;