1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 21:29:42 +02:00

Allow content to contain links to network locations

This commit is contained in:
Harvey Kandola 2018-07-09 14:41:55 -04:00
parent 19736aab04
commit 4cfbd57871
13 changed files with 129 additions and 30 deletions

View file

@ -3,4 +3,8 @@
-- max tags per document setting -- max tags per document setting
ALTER TABLE organization ADD COLUMN `maxtags` INT NOT NULL DEFAULT 3 AFTER `authconfig`; ALTER TABLE organization ADD COLUMN `maxtags` INT NOT NULL DEFAULT 3 AFTER `authconfig`;
-- support for network location link types
ALTER TABLE link ADD COLUMN `externalid` NVARCHAR(1000) NOT NULL DEFAULT '' AFTER `targetid`;
-- deprecations -- deprecations
ALTER TABLE organization DROP COLUMN `url`;

View file

@ -67,6 +67,8 @@ func getLink(t html.Token) (ok bool, link link.Link) {
link.TargetID = strings.TrimSpace(a.Val) link.TargetID = strings.TrimSpace(a.Val)
case "data-link-type": case "data-link-type":
link.LinkType = strings.TrimSpace(a.Val) link.LinkType = strings.TrimSpace(a.Val)
case "data-external-id":
link.ExternalID = strings.TrimSpace(a.Val)
} }
} }

View file

@ -36,8 +36,8 @@ func (s Scope) Add(ctx domain.RequestContext, l link.Link) (err error) {
l.Created = time.Now().UTC() l.Created = time.Now().UTC()
l.Revised = time.Now().UTC() l.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", _, err = ctx.Transaction.Exec("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, externalid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised) l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.ExternalID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute link insert") err = errors.Wrap(err, "execute link insert")
@ -49,7 +49,7 @@ func (s Scope) Add(ctx domain.RequestContext, l link.Link) (err error) {
// GetDocumentOutboundLinks returns outbound links for specified document. // GetDocumentOutboundLinks returns outbound links for specified document.
func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID string) (links []link.Link, err error) { func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links, err = s.Runtime.Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised `select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.externalid, l.linktype, l.orphan, l.created, l.revised
FROM link l FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=?`, WHERE l.orgid=? AND l.sourcedocumentid=?`,
ctx.OrgID, ctx.OrgID,
@ -70,7 +70,7 @@ func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID st
// GetPageLinks returns outbound links for specified page in document. // GetPageLinks returns outbound links for specified page in document.
func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string) (links []link.Link, err error) { func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links, err = s.Runtime.Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised `select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.externalid, l.linktype, l.orphan, l.created, l.revised
FROM link l FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`, WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`,
ctx.OrgID, ctx.OrgID,

View file

@ -36,8 +36,8 @@ func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization)
org.Revised = time.Now().UTC() org.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec( _, err = ctx.Transaction.Exec(
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, maxtags, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO organization (refid, company, title, message, domain, email, allowanonymousaccess, serial, maxtags, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.URL), strings.ToLower(org.Domain), org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.Domain),
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised) strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised)
if err != nil { if err != nil {
@ -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. // 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) { 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, maxtags, created, revised FROM organization WHERE refid=?") stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, 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) defer streamutil.Close(stmt)
if err != nil { if err != nil {
@ -80,14 +80,14 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
} }
// match on given domain name // 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, maxtags, created, revised FROM organization WHERE domain=? AND active=1", subdomain) err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, 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 { if err == nil {
return return
} }
err = nil err = nil
// match on empty domain as last resort // 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, maxtags, created, revised FROM organization WHERE domain='' AND active=1") err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, 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 { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "unable to execute select for empty subdomain") err = errors.Wrap(err, "unable to execute select for empty subdomain")
} }

View file

@ -464,14 +464,21 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
link.SourceDocumentID = model.Page.DocumentID link.SourceDocumentID = model.Page.DocumentID
link.SourcePageID = model.Page.RefID link.SourcePageID = model.Page.RefID
if link.LinkType == "document" { if link.LinkType == "document" || link.LinkType == "network" {
link.TargetID = "" link.TargetID = ""
} }
if link.LinkType != "network" {
link.ExternalID = ""
}
// We check if there was a previously saved version of this link. // We check if there was a previously saved version of this link.
// If we find one, we carry forward the orphan flag. // If we find one, we carry forward the orphan flag.
for _, p := range previousLinks { for _, p := range previousLinks {
if link.TargetID == p.TargetID && link.LinkType == p.LinkType { if link.LinkType == p.LinkType && link.TargetID == p.TargetID && link.LinkType != "network" {
link.Orphan = p.Orphan
break
}
if link.LinkType == p.LinkType && link.ExternalID == p.ExternalID && link.LinkType == "network" {
link.Orphan = p.Orphan link.Orphan = p.Orphan
break break
} }

View file

@ -12,9 +12,10 @@
import { debounce } from '@ember/runloop'; import { debounce } from '@ember/runloop';
import { computed, set } from '@ember/object'; import { computed, set } from '@ember/object';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component'; import stringUtil from '../../utils/string';
import TooltipMixin from '../../mixins/tooltip'; import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, { export default Component.extend(ModalMixin, TooltipMixin, {
link: service(), link: service(),
@ -26,6 +27,8 @@ export default Component.extend(ModalMixin, TooltipMixin, {
showSections: computed('tab1Selected', function() { return this.get('tab1Selected'); }), showSections: computed('tab1Selected', function() { return this.get('tab1Selected'); }),
showAttachments: computed('tab2Selected', function() { return this.get('tab2Selected'); }), showAttachments: computed('tab2Selected', function() { return this.get('tab2Selected'); }),
showSearch: computed('tab3Selected', function() { return this.get('tab3Selected'); }), showSearch: computed('tab3Selected', function() { return this.get('tab3Selected'); }),
showNetwork: computed('tab4Selected', function() { return this.get('tab4Selected'); }),
networkLocation: '',
keywords: '', keywords: '',
hasMatches: computed('matches', function () { hasMatches: computed('matches', function () {
let m = this.get('matches'); let m = this.get('matches');
@ -66,6 +69,8 @@ export default Component.extend(ModalMixin, TooltipMixin, {
didRender() { didRender() {
this._super(...arguments); this._super(...arguments);
this.$('#content-linker-networklocation').removeClass('is-invalid');
this.renderTooltips(); this.renderTooltips();
}, },
@ -114,7 +119,25 @@ export default Component.extend(ModalMixin, TooltipMixin, {
onInsertLink() { onInsertLink() {
let selection = this.get('selection'); let selection = this.get('selection');
if (this.get('tab4Selected')) {
let loc = this.get('networkLocation').trim();
let folderId = this.get('folder.id');
let documentId = this.get('document.id');
selection = {
context: '',
documentId: documentId,
folderId: folderId,
id: stringUtil.makeId(16),
linkType: 'network',
targetId: '',
externalId: loc,
title: loc
}
}
if (is.null(selection)) { if (is.null(selection)) {
if (this.get('tab4Selected')) this.$('#content-linker-networklocation').addClass('is-invalid').focus();
return; return;
} }
@ -125,6 +148,7 @@ export default Component.extend(ModalMixin, TooltipMixin, {
this.set('tab1Selected', id === 1); this.set('tab1Selected', id === 1);
this.set('tab2Selected', id === 2); this.set('tab2Selected', id === 2);
this.set('tab3Selected', id === 3); this.set('tab3Selected', id === 3);
this.set('tab4Selected', id === 4);
} }
} }
}); });

View file

@ -17,6 +17,10 @@ export default Component.extend({
classNames: ['layout-footer', 'non-printable'], classNames: ['layout-footer', 'non-printable'],
tagName: 'footer', tagName: 'footer',
appMeta: service(), appMeta: service(),
showWait: false,
showDone: false,
showMessage: false,
message: '',
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -40,5 +44,17 @@ export default Component.extend({
$('.progress-done').removeClass('zoomIn').addClass('zoomOut'); $('.progress-done').removeClass('zoomIn').addClass('zoomOut');
}, 3000); }, 3000);
} }
if (msg !== 'done' && msg !== 'wait') {
$('.progress-notification').removeClass('zoomOut').addClass('zoomIn');
this.set('showWait', false);
this.set('showDone', false);
this.set('showMessage', true);
this.set('message', msg);
setTimeout(function() {
$('.progress-notification').removeClass('zoomIn').addClass('zoomOut');
}, 3000);
}
} }
}); });

View file

@ -10,12 +10,14 @@
// https://documize.com // https://documize.com
import Service, { inject as service } from '@ember/service'; import Service, { inject as service } from '@ember/service';
import Notifier from '../mixins/notifier';
export default Service.extend({ export default Service.extend(Notifier, {
sessionService: service('session'), sessionService: service('session'),
ajax: service(), ajax: service(),
appMeta: service(), appMeta: service(),
store: service(), store: service(),
eventBus: service(),
// Returns links within specified document // Returns links within specified document
getDocumentLinks(documentId) { getDocumentLinks(documentId) {
@ -67,6 +69,10 @@ export default Service.extend({
href = `${endpoint}/public/attachments/${orgId}/${link.targetId}`; href = `${endpoint}/public/attachments/${orgId}/${link.targetId}`;
result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-target-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`; result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-target-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
} }
if (link.linkType === "network") {
href = `fileto://${link.externalId}`;
result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-target-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-external-id='${link.externalId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
}
return result; return result;
}, },
@ -78,11 +84,12 @@ export default Service.extend({
documentId: a.attributes["data-link-target-document-id"].value, documentId: a.attributes["data-link-target-document-id"].value,
folderId: a.attributes["data-link-space-id"].value, folderId: a.attributes["data-link-space-id"].value,
targetId: a.attributes["data-link-target-id"].value, targetId: a.attributes["data-link-target-id"].value,
externalId: a.attributes["data-link-external-id"].value,
url: a.attributes["href"].value, url: a.attributes["href"].value,
orphan: false orphan: false
}; };
link.orphan = _.isEmpty(link.linkId) || _.isEmpty(link.documentId) || _.isEmpty(link.folderId) || _.isEmpty(link.targetId); link.orphan = _.isEmpty(link.linkId) || _.isEmpty(link.documentId) || _.isEmpty(link.folderId) || (_.isEmpty(link.targetId) && _.isEmpty(link.externalId));
// we check latest state of link using database data // we check latest state of link using database data
let existing = outboundLinks.findBy('id', link.linkId); let existing = outboundLinks.findBy('id', link.linkId);
@ -126,5 +133,23 @@ export default Service.extend({
window.location.href = link.url; window.location.href = link.url;
return; return;
} }
// handle network share/drive links
if (link.linkType === "network") {
// window.location.href = link.externalId;
const el = document.createElement('textarea');
el.value = link.externalId;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
this.showNotification('Copied location to clipboard');
return;
}
} }
}); });

View file

@ -22,6 +22,7 @@ footer {
> .progress { > .progress {
display: inline-block; display: inline-block;
text-align: right;
> img { > img {
padding: 0; padding: 0;
@ -40,4 +41,11 @@ footer {
width: 20px; width: 20px;
@include border-radius(20px); @include border-radius(20px);
} }
> .progress-notification {
display: inline-block;
font-size: 1rem;
color: $color-green;
font-weight: 500;
}
} }

View file

@ -3,15 +3,14 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header">Insert Link</div> <div class="modal-header">Insert Link</div>
<div class="modal-body"> <div class="modal-body">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col mt-3 mb-5"> <div class="col mt-3 mb-5">
<ul class="tabnav-control text-center"> <ul class="tabnav-control text-center">
<li class="tab {{if tab1Selected 'selected'}}" {{action 'onTabSelect' 1}}>Section</li> <li class="tab {{if tab1Selected 'selected'}}" {{action 'onTabSelect' 1}}>Section</li>
<li class="tab {{if tab2Selected 'selected'}}" {{action 'onTabSelect' 2}}>Attachment</li> <li class="tab {{if tab2Selected 'selected'}}" {{action 'onTabSelect' 2}}>Attachment</li>
<li class="tab {{if tab3Selected 'selected'}}" {{action 'onTabSelect' 3}}>Search</li> <li class="tab {{if tab3Selected 'selected'}}" {{action 'onTabSelect' 3}}>Search</li>
<li class="tab {{if tab4Selected 'selected'}}" {{action 'onTabSelect' 4}}>Network</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -98,9 +97,20 @@
</div> </div>
{{/if}} {{/if}}
{{#if showNetwork}}
<div class="row">
<div class="col content-linker-modal-container">
<p>Specify network drive/share/folder location</p>
<div class="form-group">
{{focus-input id="content-linker-networklocation" type="input" class="form-control" value=networkLocation placeholder="e.g. //share/folder" autocomplete="off"}}
<small class="form-text text-muted"></small>
</div> </div>
</div>
</div>
{{/if}}
</div> </div>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" {{action 'onCancel'}}>Cancel</button> <button type="button" class="btn btn-outline-secondary" {{action 'onCancel'}}>Cancel</button>
<button type="button" class="btn btn-success" {{action 'onInsertLink'}}>Insert</button> <button type="button" class="btn btn-success" {{action 'onInsertLink'}}>Insert</button>

View file

@ -1,5 +1,11 @@
<div class="row no-gutters d-flex align-items-center"> <div class="row no-gutters d-flex align-items-center">
<div class="col d-flex justify-content-start"> <div class="col d-flex justify-content-start">
<div class="footer">
<a href="https://documize.com?ref=af">Documize {{appMeta.version}}</a>
</div>
{{yield}}
</div>
<div class="col d-flex justify-content-end">
<div class="footer"> <div class="footer">
{{#if showWait}} {{#if showWait}}
<div class="progress progress-wait animated fadeIn"> <div class="progress progress-wait animated fadeIn">
@ -9,12 +15,9 @@
{{#if showDone}} {{#if showDone}}
<div class="progress progress-done animated zoomIn">&check;</div> <div class="progress progress-done animated zoomIn">&check;</div>
{{/if}} {{/if}}
</div> {{#if showMessage}}
{{yield}} <div class="progress-notification animated zoomIn">{{message}}</div>
</div> {{/if}}
<div class="col d-flex justify-content-end">
<div class="footer">
<a href="https://documize.com?ref=af">Documize {{appMeta.version}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -24,6 +24,7 @@ type Link struct {
SourcePageID string `json:"sourcePageId"` SourcePageID string `json:"sourcePageId"`
TargetDocumentID string `json:"targetDocumentId"` TargetDocumentID string `json:"targetDocumentId"`
TargetID string `json:"targetId"` TargetID string `json:"targetId"`
ExternalID string `json:"externalId"`
Orphan bool `json:"orphan"` Orphan bool `json:"orphan"`
} }

View file

@ -19,7 +19,6 @@ type Organization struct {
Company string `json:"-"` Company string `json:"-"`
Title string `json:"title"` Title string `json:"title"`
Message string `json:"message"` Message string `json:"message"`
URL string `json:"url"`
Domain string `json:"domain"` Domain string `json:"domain"`
Email string `json:"email"` Email string `json:"email"`
AllowAnonymousAccess bool `json:"allowAnonymousAccess"` AllowAnonymousAccess bool `json:"allowAnonymousAccess"`