mirror of
https://github.com/documize/community.git
synced 2025-07-19 05:09:42 +02:00
New config setting: how many tags per document?
This commit is contained in:
parent
0743ae002c
commit
19736aab04
17 changed files with 827 additions and 731 deletions
|
@ -58,9 +58,9 @@ Space view.
|
|||
|
||||
## Latest version
|
||||
|
||||
[Community edition: v1.66.0](https://github.com/documize/community/releases)
|
||||
[Community edition: v1.67.0](https://github.com/documize/community/releases)
|
||||
|
||||
[Enterprise edition: v1.68.0](https://documize.com/downloads)
|
||||
[Enterprise edition: v1.69.0](https://documize.com/downloads)
|
||||
|
||||
## OS support
|
||||
|
||||
|
|
6
core/database/scripts/autobuild/db_00024.sql
Normal file
6
core/database/scripts/autobuild/db_00024.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* community edition */
|
||||
|
||||
-- max tags per document setting
|
||||
ALTER TABLE organization ADD COLUMN `maxtags` INT NOT NULL DEFAULT 3 AFTER `authconfig`;
|
||||
|
||||
-- deprecations
|
|
@ -54,6 +54,7 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
|
|||
data.AllowAnonymousAccess = org.AllowAnonymousAccess
|
||||
data.AuthProvider = org.AuthProvider
|
||||
data.AuthConfig = org.AuthConfig
|
||||
data.MaxTags = org.MaxTags
|
||||
data.Version = h.Runtime.Product.Version
|
||||
data.Edition = h.Runtime.Product.License.Edition
|
||||
data.Valid = h.Runtime.Product.License.Valid
|
||||
|
|
|
@ -36,9 +36,9 @@ func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization)
|
|||
org.Revised = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.Exec(
|
||||
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, maxtags, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.URL), strings.ToLower(org.Domain),
|
||||
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.Created, org.Revised)
|
||||
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to execute insert for org")
|
||||
|
@ -49,7 +49,7 @@ func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization)
|
|||
|
||||
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
|
||||
func (s Scope) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
|
||||
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE refid=?")
|
||||
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE refid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -80,14 +80,14 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
|||
}
|
||||
|
||||
// match on given domain name
|
||||
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain=? AND active=1", subdomain)
|
||||
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE domain=? AND active=1", subdomain)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
// match on empty domain as last resort
|
||||
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain='' AND active=1")
|
||||
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE domain='' AND active=1")
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
err = errors.Wrap(err, "unable to execute select for empty subdomain")
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
|||
func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
|
||||
org.Revised = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid",
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, maxtags=:maxtags, revised=:revised WHERE refid=:refid",
|
||||
&org)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -41,7 +41,7 @@ func main() {
|
|||
// product details
|
||||
rt.Product = env.ProdInfo{}
|
||||
rt.Product.Major = "1"
|
||||
rt.Product.Minor = "66"
|
||||
rt.Product.Minor = "67"
|
||||
rt.Product.Patch = "0"
|
||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||
rt.Product.Edition = "Community"
|
||||
|
|
1368
embed/bindata.go
1368
embed/bindata.go
File diff suppressed because one or more lines are too long
|
@ -17,6 +17,7 @@ import Notifier from '../../mixins/notifier';
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend(Notifier, {
|
||||
maxTags: 3,
|
||||
titleEmpty: empty('model.general.title'),
|
||||
messageEmpty: empty('model.general.message'),
|
||||
conversionEndpointEmpty: empty('model.general.conversionEndpoint'),
|
||||
|
@ -24,7 +25,19 @@ export default Component.extend(Notifier, {
|
|||
hasMessageInputError: and('messageEmpty', 'messageError'),
|
||||
hasConversionEndpointInputError: and('conversionEndpointEmpty', 'conversionEndpointError'),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.set('maxTags', this.get('model.general.maxTags'));
|
||||
},
|
||||
|
||||
actions: {
|
||||
change() {
|
||||
const selectEl = this.$('#maxTags')[0];
|
||||
const selection = selectEl.selectedOptions[0].value;
|
||||
|
||||
this.set('maxTags', parseInt(selection));
|
||||
},
|
||||
|
||||
save() {
|
||||
if (isEmpty(this.get('model.general.title'))) {
|
||||
set(this, 'titleError', true);
|
||||
|
@ -46,6 +59,7 @@ export default Component.extend(Notifier, {
|
|||
this.set('model.general.conversionEndpoint', e.substring(0, e.length-1));
|
||||
}
|
||||
|
||||
this.set('model.general.maxTags', this.get('maxTags'));
|
||||
this.model.general.set('allowAnonymousAccess', $("#allowAnonymousAccess").prop('checked'));
|
||||
|
||||
this.showWait();
|
||||
|
|
|
@ -18,9 +18,11 @@ import Notifier from '../../mixins/notifier';
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend(Notifier, {
|
||||
appMeta: service(),
|
||||
documentSvc: service('document'),
|
||||
categoryService: service('category'),
|
||||
|
||||
tagz: A([]),
|
||||
categories: A([]),
|
||||
newCategory: '',
|
||||
showCategoryModal: false,
|
||||
|
@ -34,11 +36,6 @@ export default Component.extend(Notifier, {
|
|||
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
|
||||
}),
|
||||
|
||||
maxTags: 3,
|
||||
tag1: '',
|
||||
tag2: '',
|
||||
tag3: '',
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.load();
|
||||
|
@ -48,7 +45,7 @@ export default Component.extend(Notifier, {
|
|||
this._super(...arguments);
|
||||
|
||||
schedule('afterRender', () => {
|
||||
$("#add-tag-field0").focus();
|
||||
$("#add-tag-field-1").focus();
|
||||
|
||||
$(".tag-input").off("keydown").on("keydown", function(e) {
|
||||
if (e.shiftKey && e.which === 9) {
|
||||
|
@ -59,7 +56,20 @@ export default Component.extend(Notifier, {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (e.which === 9 || e.which === 13 || e.which === 16 || e.which === 45 || e.which === 189 || e.which === 8 || e.which === 127 || (e.which >= 65 && e.which <= 90) || (e.which >= 97 && e.which <= 122) || (e.which >= 48 && e.which <= 57)) {
|
||||
if (e.which === 9 ||
|
||||
e.which === 13 ||
|
||||
e.which === 16 ||
|
||||
e.which === 37 ||
|
||||
e.which === 38 ||
|
||||
e.which === 39 ||
|
||||
e.which === 40 ||
|
||||
e.which === 45 ||
|
||||
e.which === 189 ||
|
||||
e.which === 8 ||
|
||||
e.which === 127 ||
|
||||
(e.which >= 65 && e.which <= 90) ||
|
||||
(e.which >= 97 && e.which <= 122) ||
|
||||
(e.which >= 48 && e.which <= 57)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -90,17 +100,27 @@ export default Component.extend(Notifier, {
|
|||
});
|
||||
});
|
||||
|
||||
let counter = 1;
|
||||
let tagz = A([]);
|
||||
let maxTags = this.get('appMeta.maxTags');
|
||||
|
||||
if (!_.isUndefined(this.get('document.tags')) && this.get('document.tags').length > 1) {
|
||||
let tags = this.get('document.tags').split('#');
|
||||
let counter = 1;
|
||||
|
||||
_.each(tags, (tag) => {
|
||||
tag = tag.trim();
|
||||
if (tag.length > 0) {
|
||||
this.set('tag' + counter, tag);
|
||||
if (tag.length > 0 && counter <= maxTags) {
|
||||
tagz.pushObject({number: counter, value: tag});
|
||||
counter++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let index = counter; index <= maxTags; index++) {
|
||||
tagz.pushObject({number: index, value: ''});
|
||||
}
|
||||
|
||||
this.set('tagz', tagz);
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
@ -142,27 +162,26 @@ export default Component.extend(Notifier, {
|
|||
});
|
||||
});
|
||||
|
||||
let tag1 = this.get("tag1").toLowerCase().trim();
|
||||
let tag2 = this.get("tag2").toLowerCase().trim();
|
||||
let tag3 = this.get("tag3").toLowerCase().trim();
|
||||
let tagz = this.get('tagz');
|
||||
let tagzToSave = [];
|
||||
|
||||
_.each(tagz, (t) => {
|
||||
let tag = t.value.toLowerCase().trim();
|
||||
if (tag.length> 0) {
|
||||
if (!_.contains(tagzToSave, tag) && is.not.startWith(tag, '-')) {
|
||||
tagzToSave.push(tag);
|
||||
this.$('#add-tag-field-' + t.number).removeClass('is-invalid');
|
||||
} else {
|
||||
this.$('#add-tag-field-' + t.number).addClass('is-invalid');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let save = "#";
|
||||
|
||||
if (is.startWith(tag1, '-')) {
|
||||
$('#add-tag-field1').addClass('is-invalid');
|
||||
return;
|
||||
}
|
||||
if (is.startWith(tag2, '-')) {
|
||||
$('#add-tag-field2').addClass('is-invalid');
|
||||
return;
|
||||
}
|
||||
if (is.startWith(tag3, '-')) {
|
||||
$('#add-tag-field3').addClass('is-invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
(tag1.length > 0 ) ? save += (tag1 + "#") : this.set('tag1', '');
|
||||
(tag2.length > 0 && tag2 !== tag1) ? save += (tag2 + "#") : this.set('tag2', '');
|
||||
(tag3.length > 0 && tag3 !== tag1 && tag3 !== tag2) ? save += (tag3 + "#") : this.set('tag3', '');
|
||||
_.each(tagzToSave, (t) => {
|
||||
save += t;
|
||||
save += '#';
|
||||
});
|
||||
|
||||
let doc = this.get('document');
|
||||
doc.set('tags', save);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
// import { belongsTo, hasMany } from 'ember-data/relationships';
|
||||
|
||||
export default Model.extend({
|
||||
title: attr('string'),
|
||||
|
@ -19,6 +18,7 @@ export default Model.extend({
|
|||
email: attr('string'),
|
||||
conversionEndpoint: attr('string'),
|
||||
allowAnonymousAccess: attr('boolean', { defaultValue: false }),
|
||||
maxTags: attr('number', {defaultValue: 3}),
|
||||
created: attr(),
|
||||
revised: attr()
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ export default Service.extend({
|
|||
message: '',
|
||||
edition: 'Community',
|
||||
// for major.minor semver release detection
|
||||
// for bugfix releases, only admin is made aware of new release and end users see no Whats New messaging
|
||||
// for bugfix releases, only admin is made aware of new release and end users see no What's New messaging
|
||||
updateAvailable: false,
|
||||
valid: true,
|
||||
allowAnonymousAccess: false,
|
||||
|
@ -37,6 +37,7 @@ export default Service.extend({
|
|||
authConfig: null,
|
||||
setupMode: false,
|
||||
secureMode: false,
|
||||
maxTags: 3,
|
||||
|
||||
invalidLicense() {
|
||||
return this.valid === false;
|
||||
|
|
|
@ -34,6 +34,7 @@ export default Service.extend({
|
|||
this.get('appMeta').setProperties({
|
||||
message: org.get('message'),
|
||||
title: org.get('title'),
|
||||
maxTags: org.get('maxTags'),
|
||||
conversionEndpoint: org.get('conversionEndpoint')
|
||||
});
|
||||
|
||||
|
|
|
@ -10,22 +10,22 @@
|
|||
<div class="view-customize">
|
||||
<form class="mt-5">
|
||||
<div class="form-group row">
|
||||
<label for="siteTitle" class="col-sm-2 col-form-label">Instance Name</label>
|
||||
<div class="col-sm-10">
|
||||
<label for="siteTitle" class="col-sm-4 col-form-label">Site Name</label>
|
||||
<div class="col-sm-7">
|
||||
{{focus-input id="siteTitle" type="text" value=model.general.title class=(if hasTitleInputError 'form-control is-invalid' 'form-control')}}
|
||||
<small class="form-text text-muted">Provide short title for this Documize instance</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label id="siteMessage" class="col-sm-2 col-form-label">Message</label>
|
||||
<div class="col-sm-10">
|
||||
<label id="siteMessage" class="col-sm-4 col-form-label">Site Message</label>
|
||||
<div class="col-sm-7">
|
||||
{{textarea id="siteMessage" rows="3" value=model.general.message class=(if hasMessageInputError 'form-control is-invalid' 'form-control')}}
|
||||
<small class="form-text text-muted">Provide short message explaining this Documize instance</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">Anonymous Access</label>
|
||||
<div class="col-sm-10">
|
||||
<label class="col-sm-4 col-form-label">Anonymous Access</label>
|
||||
<div class="col-sm-7">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="allowAnonymousAccess" checked= {{model.general.allowAnonymousAccess}}
|
||||
/>
|
||||
|
@ -36,8 +36,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="conversionEndpoint" class="col-sm-2 col-form-label">Conversion Service URL</label>
|
||||
<div class="col-sm-10">
|
||||
<label for="conversionEndpoint" class="col-sm-4 col-form-label">Conversion Service URL</label>
|
||||
<div class="col-sm-7">
|
||||
{{input id="conversionEndpoint" type="text" value=model.general.conversionEndpoint class=(if hasConversionEndpointInputError 'form-control is-invalid' 'form-control')}}
|
||||
<small class="form-text text-muted">
|
||||
Endpoint for handling import/export (e.g. https://api.documize.com,
|
||||
|
@ -45,6 +45,22 @@
|
|||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn btn-success mt-4" {{action 'save'}}>Save</div>
|
||||
<div class="form-group row">
|
||||
<label for="maxTags" class="col-sm-4 col-form-label">Maximum Tags Per Document</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="maxTags" {{action 'change' on='change'}}>
|
||||
<option selected={{is-equal 3 maxTags}} value=3>3</option>
|
||||
<option selected={{is-equal 4 maxTags}} value=4>4</option>
|
||||
<option selected={{is-equal 5 maxTags}} value=5>5</option>
|
||||
<option selected={{is-equal 6 maxTags}} value=6>6</option>
|
||||
<option selected={{is-equal 7 maxTags}} value=7>7</option>
|
||||
<option selected={{is-equal 8 maxTags}} value=8>8</option>
|
||||
<option selected={{is-equal 9 maxTags}} value=9>9</option>
|
||||
<option selected={{is-equal 10 maxTags}} value=10>10</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">How many tags can be assigned to a document (between 3 and 10 tags)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn btn-success font-weight-bold text-uppercase mt-4" {{action 'save'}}>Save</div>
|
||||
</form>
|
||||
</div>
|
|
@ -2,27 +2,17 @@
|
|||
<div class="explainer-header">Categories & Tags</div>
|
||||
<p class="explainer-text explainer-gap">Categorize your content, assign tags to suppliment</p>
|
||||
|
||||
<h1>Specify up to three tags</h1>
|
||||
<h1>Specify up to {{appMeta.maxTags}} tags</h1>
|
||||
<p class="form-text text-muted">Lowercase, characters, numbers, hyphens only</p>
|
||||
<form>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">#</span>
|
||||
{{#each tagz as |tag|}}
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">#</span>
|
||||
</div>
|
||||
{{input type='text' id=(concat 'add-tag-field-' tag.number) class="form-control mousetrap tag-input" placeholder="Tag name" value=tag.value}}
|
||||
</div>
|
||||
{{input type='text' id='add-tag-field1' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag1}}
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">#</span>
|
||||
</div>
|
||||
{{input type='text' id='add-tag-field2' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag2}}
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">#</span>
|
||||
</div>
|
||||
{{input type='text' id='add-tag-field3' class="form-control mousetrap tag-input" placeholder="Tag name" value=tag3}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</form>
|
||||
|
||||
<div class="mt-5" />
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<td>{{x-toggle value=permission.spaceView onToggle=(action (mut permission.spaceView))}}</td>
|
||||
<td>{{x-toggle value=permission.spaceManage onToggle=(action (mut permission.spaceManage))}}</td>
|
||||
<td>{{x-toggle value=permission.spaceOwner onToggle=(action (mut permission.spaceOwner))}}</td>
|
||||
<td>{{x-toggle value=permission.documentAdd onToggle=(action (mut permission.spacdocumentAddView))}}</td>
|
||||
<td>{{x-toggle value=permission.documentAdd onToggle=(action (mut permission.documentAdd))}}</td>
|
||||
<td>{{x-toggle value=permission.documentEdit onToggle=(action (mut permission.documentEdit))}}</td>
|
||||
<td>{{x-toggle value=permission.documentDelete onToggle=(action (mut permission.documentDelete))}}</td>
|
||||
<td>{{x-toggle value=permission.documentMove onToggle=(action (mut permission.documentMove))}}</td>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "documize",
|
||||
"version": "1.66.0",
|
||||
"version": "1.67.0",
|
||||
"description": "The Document IDE",
|
||||
"private": true,
|
||||
"repository": "",
|
||||
|
|
|
@ -32,6 +32,7 @@ type SiteMeta struct {
|
|||
AuthProvider string `json:"authProvider"`
|
||||
AuthConfig string `json:"authConfig"`
|
||||
Version string `json:"version"`
|
||||
MaxTags int `json:"maxTags"`
|
||||
Edition string `json:"edition"`
|
||||
Valid bool `json:"valid"`
|
||||
ConversionEndpoint string `json:"conversionEndpoint"`
|
||||
|
|
|
@ -26,6 +26,7 @@ type Organization struct {
|
|||
AuthProvider string `json:"authProvider"`
|
||||
AuthConfig string `json:"authConfig"`
|
||||
ConversionEndpoint string `json:"conversionEndpoint"`
|
||||
MaxTags int `json:"maxTags"`
|
||||
Serial string `json:"-"`
|
||||
Active bool `json:"-"`
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue