Skip to content

Commit

Permalink
fix scene access rights button display and restore archive/delete fun…
Browse files Browse the repository at this point in the history
…ctions
  • Loading branch information
sdumetz committed Feb 3, 2025
1 parent 7c965f3 commit dbf7381
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 53 deletions.
6 changes: 5 additions & 1 deletion source/server/routes/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ routes.get("/admin/stats", (req, res)=>{
});
});

//Ensure no unauthorized access
//Additionally, sets res.locals.access, required for the "scene" template
routes.use("/scenes/:scene", canRead);

routes.get("/scenes/:scene", wrap(async (req, res)=>{
Expand All @@ -237,6 +239,8 @@ routes.get("/scenes/:scene", wrap(async (req, res)=>{
return res;
}).map(t=>t.name);

console.log("Access : ", res.locals.access);

res.render("scene", {
title: `eCorpus: ${scene.name}`,
scene,
Expand Down Expand Up @@ -282,7 +286,7 @@ routes.get("/scenes/:scene/edit", canWrite, (req, res)=>{
let host = getHost(req);
let referrer = new URL(req.get("Referrer")||`/ui/scenes/`, host);
let thumb = new URL(`/scenes/${encodeURIComponent(scene)}/scene-image-thumb.jpg`, host);

res.render("story", {
title: `${scene}: Story Editor`,
layout: "viewer",
Expand Down
2 changes: 1 addition & 1 deletion source/server/templates/history.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

<h1>{{i18n "titles.history"}}</h1>
<div class="section">
<a href="/ui/scenes/{{encodeURIComponent name}}"><h2>{{name}}</h2></a>
<h2><a href="/ui/scenes/{{encodeURIComponent name}}">{{name}}</a></h2>
<p>{{{i18n "leads.historyEdition"}}}</p>
<scene-history name="{{encodeURIComponent name}}"></scene-history>
</div>
2 changes: 1 addition & 1 deletion source/server/templates/partials/sceneCard.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<ui-icon name="eye"></ui-icon>
<span class="tool-text">{{i18n "labels.view"}}</span>
</a>
{{#unless (test access.user "==" "read")}}
{{#unless (test @root.access "==" "read")}}
<a class="tool-link" href="/ui/scenes/{{encodeURIComponent name}}/edit" title="{{i18n "leads.editScene"}}">
<ui-icon name="edit"></ui-icon>
<span class="tool-text">{{i18n "labels.edit"}}</span>
Expand Down
36 changes: 19 additions & 17 deletions source/server/templates/scene.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
{{#each scene.tags}}
<a class="btn btn-main btn-small" href="/ui/tags/{{.}}">{{.}}</a>
{{/each}}
{{#if (test scene.access.user "==" "admin")}}
{{#if (test access "==" "admin")}}
<span style="flex-grow: 1; text-align:right; margin-right: 1rem">
<a href="#edit-tags" >{{i18n "buttons.editTags"}}</a>
</span>
{{/if}}
</div>
</div>
{{#if (test scene.access.user "==" "admin")}}
{{#if (test access "==" "admin")}}
<div id="edit-tags" class="tab">
<submit-fragment submit="tags" method="PATCH" action="/scenes/{{encodeURIComponent scene.name}}" encoding="application/json" onsubmit="window.location.reload()">
<form autocomplete="off" class="form-control tags-list">
Expand Down Expand Up @@ -63,7 +63,7 @@
{{/if}}
</div>
<div class="col" style="display:flex;flex-direction: column; gap: 10px; align-items: stretch;">
{{#unless (test access.user "==" "read")}}
{{#unless (test access "==" "read")}}
<a class="btn btn-main" href="/ui/scenes/{{encodeURIComponent scene.name}}/edit" title="{{i18n "leads.showScene"}}">
<ui-icon name="edit"></ui-icon> {{i18n "labels.edit"}}
</a>
Expand All @@ -78,7 +78,7 @@
<span class="tool-text">{{i18n "buttons.download"}}</span>
</a>

{{#if (test scene.access.user "==" "admin") }}
{{#if (test access "==" "admin") }}
<a href="/ui/scenes/{{encodeURIComponent scene.name }}/history" class="btn btn-main">
<ui-icon name="hierarchy"></ui-icon>
{{i18n "buttons.history"}}
Expand Down Expand Up @@ -146,7 +146,7 @@

{{#each permissions}}
<tr>
{{#if (test @root.user.isDefaultUser "||" (test @root.scene.access.user "!=" "admin") ) }}
{{#if (test access "!=" "admin") }}
<td class="text-main" title="{{i18n "label.username"}}">{{> showName}}</td>
<td class="text-main" >{{access}}</td>
{{else if (test (test username "==" @root.user.username) "&&" (test "!" @root.user.isAdministrator ))}}
Expand Down Expand Up @@ -175,7 +175,7 @@
{{/each}}
</tbody>
</table>
{{#if (test scene.access.user "==" "admin")}}
{{#if (test access "==" "admin")}}
<submit-fragment style="width:100%;margin-top: 1rem;" method="PATCH" action="/auth/access/{{encodeURIComponent @root.scene.name}}" encoding="application/json" onsubmit="window.location.reload()">
<form id="addUserRights" class="form-control" style="padding:0;flex-grow: 1" autocomplete="off" name="">
<div class="form-group" style="padding:0;border:none;">
Expand All @@ -195,23 +195,25 @@
</div>
</section>

{{#if (test scene.access.user "==" "admin")}}
{{#if (test access "==" "admin")}}
<section>
<p >
{{i18n "leads.archiveDelete"}}
</p>
<div style="padding: 0 0 10px 0;display:flex;justify-content:end;gap:10px;">
<button class="btn" icon="edit">
<ui-icon name="trash"></ui-icon>
{{i18n "buttons.archive"}}
</button>
{{#if user.isAdministrator }}
<button class="btn btn-danger" icon="edit">
<submit-fragment method="DELETE" encoding="application/json" onsubmit="window.location.href='/ui/'">
<form style="padding: 0 0 10px 0;display:flex;justify-content:end;gap:10px;">
<button role="submit" formAction="/scenes/{{encodeURIComponent scene.name}}?archive=true" id="btn-archiveScene" class="btn" icon="edit" >
<ui-icon name="trash"></ui-icon>
{{i18n "buttons.delete"}}
{{i18n "buttons.archive"}}
</button>
{{/if}}
</div>
{{#if user.isAdministrator }}
<button role="submit" formAction="/scenes/{{encodeURIComponent scene.name}}?archive=false" class="btn btn-danger" icon="edit">
<ui-icon name="trash"></ui-icon>
{{i18n "buttons.delete"}}
</button>
{{/if}}
</form>
</submit-fragment>
</section>
{{/if}}
</div>
Expand Down
5 changes: 4 additions & 1 deletion source/server/utils/locals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ function _perms(check:number,req :Request, res :Response, next :NextFunction){

res.set("Vary", "Cookie, Authorization");

if(isAdministrator) return next();
if(isAdministrator){
res.locals.access = "admin" as AccessType;
return next();
}

let userManager = getUserManager(req);
(res.locals.access?
Expand Down
45 changes: 26 additions & 19 deletions source/ui/composants/HistoryAggregation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ interface EntryDiff{
}

interface HistorySummary{
/**
* Unique ID of this summary. Ideally stable to optimize renders.
* Used to map accessibility labels
*/
id:string;
name :string|TemplateResult|TemplateResult[];
authoredBy :string|TemplateResult|TemplateResult[];
restorePoint :number;
Expand Down Expand Up @@ -78,18 +83,18 @@ export class HistoryEntryAggregate extends i18n(LitElement){
@property({attribute: true, type: Object })
entries :AggregatedEntry;

@property({attribute: "aria-expanded", reflect: true})
ariaExpanded: "true"|"false" = "false";
@property({attribute: "aria-selected", reflect: true})
ariaSelected: "true"|"false" = "false";

@state()
diff ?:Partial<EntryDiff>;

protected toggleSelect = (e?:MouseEvent)=>{
e?.stopPropagation();
if(this.ariaExpanded === "true"){
this.ariaExpanded = "false";
if(this.ariaSelected === "true"){
this.ariaSelected = "false";
}else{
this.ariaExpanded = "true";
this.ariaSelected = "true";
}
}

Expand Down Expand Up @@ -121,19 +126,19 @@ export class HistoryEntryAggregate extends i18n(LitElement){
}

protected update(changedProperties: PropertyValues): void {
if(changedProperties.has("ariaExpanded") || changedProperties.has("entries")){
if(changedProperties.has("ariaSelected") || changedProperties.has("entries")){
if(this.diff){
changedProperties.set("diff", this.diff);
this.diff = undefined;
}
if(this.ariaExpanded === "true" && this.entries.length === 1){
if(this.ariaSelected === "true" && this.entries.length === 1){
this.fetchDiff();
}
}

if(changedProperties.has("ariaExpanded")){
if(changedProperties.has("ariaSelected")){

if(this.ariaExpanded === "true"){
if(this.ariaSelected === "true"){
this.classList.add("active");
}else{
this.classList.remove("active");
Expand Down Expand Up @@ -176,8 +181,8 @@ export class HistoryEntryAggregate extends i18n(LitElement){
}


protected renderSummary({name, restorePoint, authoredBy, from, to}:HistorySummary){
const selected = this.ariaExpanded === "true";
protected renderSummary({id, name, restorePoint, authoredBy, from, to}:HistorySummary){
const selected = this.ariaSelected === "true";

const expand = (this.entries.length === 1)?html`
<ui-button @click=${this.toggleSelect} class="btn btn-small btn-transparent btn-inline" text=${selected?"⌃":"⌄"}></ui-button>
Expand Down Expand Up @@ -225,7 +230,7 @@ export class HistoryEntryAggregate extends i18n(LitElement){
}

protected renderEntry(entry:HistoryEntry){
const selected = this.ariaExpanded === "true";
const selected = this.ariaSelected === "true";
let diff = {color:"warning", char: "~", text: this.t("ui.modified")};
if(entry.generation == 1){
diff = {color:"success", char: "+", text: this.t("ui.created")};
Expand All @@ -236,6 +241,7 @@ export class HistoryEntryAggregate extends i18n(LitElement){
<span style="flex-grow:1">${entry.name+((entry.mime =="text/directory")?"/":"")}</span>
`
let summary = this.renderSummary({
id: entry.id.toString(10),
name,
restorePoint: entry.id,
authoredBy: html`<span class="text-${diff.color}">${diff.text}</span> <i>${this.t("ui.by")}</i> <b>${entry.author}</b>`,
Expand All @@ -252,10 +258,10 @@ export class HistoryEntryAggregate extends i18n(LitElement){
if( entries.length == 1){
return this.renderEntry(entries[0]);
}
const selected = this.ariaExpanded === "true";
const selected = this.ariaSelected === "true";

if(selected){
return bucketize(entries).map((bucket, index)=>html`<history-entry-aggregate id=${this.id+"-"+index.toString(10)} aria-disabled=${index ===0? this.getAttribute("aria-disabled"): "false"} .scene=${this.scene} .entries=${bucket}></history-entry-aggregate>`);
return bucketize(entries).map((bucket, index)=>html`<history-entry-aggregate id=${this.id+"-"+index.toString(10)} role="treeitem" aria-disabled=${index ===0? this.getAttribute("aria-disabled"): "false"} .scene=${this.scene} .entries=${bucket}></history-entry-aggregate>`);
}


Expand All @@ -269,6 +275,7 @@ export class HistoryEntryAggregate extends i18n(LitElement){
}
return m;
},new Map<string, AggregatedEntry>());
let id = `${entries[0].id}-${entries[entries.length -1].id}`;
let name = (3 < names.size)?
html`<span class="expandable">${lastFile.name} </span><span class="show-more" title=${this.t("info.showDetails")} @click=${this.toggleSelect}>${this.t("info.etAl", {count:entries.length-1})}</span>`
: html`${[...names.entries()].map(([name, matches], index, a)=>{
Expand All @@ -285,7 +292,7 @@ export class HistoryEntryAggregate extends i18n(LitElement){
${authors.slice(-2).join(", ")}
<span class="show-more" title=${this.t("info.showDetails")} @click=${this.toggleSelect}>${this.t("info.etAl", {count:entries.length-2})}</span>` :
html` <i>${this.t("ui.by")}</i> <b>${authors.join(", ")}</b>`;
return this.renderSummary({name, authoredBy, showDetails: this.toggleSelect, restorePoint: lastFile.id, from: entries.slice(-1)[0].ctime, to: lastFile.ctime})
return this.renderSummary({id, name, authoredBy, showDetails: this.toggleSelect, restorePoint: lastFile.id, from: entries.slice(-1)[0].ctime, to: lastFile.ctime})
}

onRestore(id:number){
Expand All @@ -309,16 +316,16 @@ export default class HistoryAggregation extends i18n(LitElement){

const handleCollapse = (ev :MouseEvent)=>{
ev.stopPropagation();
for (let el of this.shadowRoot.querySelectorAll(`#day-${index.toString(10)} [aria-expanded="true"]`)){
el.ariaExpanded = "false";
for (let el of this.shadowRoot.querySelectorAll(`#day-${index.toString(10)} [aria-selected="true"]`)){
el.ariaSelected = "false";
}
}
return html`<div class="history-day" id=${"day-"+index.toString(10)}>
<h4 class="history-day-header" id=${"day-header-"+index.toString(10)}>
${this.t("info.changeDay", {date: day[0].ctime.toLocaleDateString(this.language)})}
</h4>
<div class="history-day-content">
<history-entry-aggregate aria-expanded="${1 <day.length?"true":"false"}" .scene=${this.scene} .entries=${day} id=${"day-group-"+index.toString(10)} aria-disabled=${index === 0? "true":"false"}></history-entry-aggregate>
<history-entry-aggregate role="treeitem" aria-selected="${1 <day.length?"true":"false"}" .scene=${this.scene} .entries=${day} id=${"day-group-"+index.toString(10)} aria-disabled=${index === 0? "true":"false"}></history-entry-aggregate>
</div>
<span class="caret" @click=${handleCollapse}></span>
</div>`;
Expand All @@ -340,7 +347,7 @@ export default class HistoryAggregation extends i18n(LitElement){
}

return html`
<div class="history-list">
<div role="tree" class="history-list">
${days.map(this.renderDay)}
</div>
`
Expand Down
38 changes: 25 additions & 13 deletions source/ui/composants/SubmitFragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,34 @@ import HttpError from "../state/HttpError";
export default class SubmitFragment extends LitElement{
/**
* Route to call. Defaults to the value of `::slotted(form).action`.
* Order of precedence upon submission is :
* - this.action
* - submitter.formAction (auto-set to form.action if an HTMLInputElement)
* - form.action (defaults to `window.location.href`)
*/
@property({attribute: true, type: String})
action ?:string;
/**
* Method to call with. Default to `::slotted(form).method`
* Useful if method is not GET or POST
* Useful if method is not GET or POST.
* Order of precedence upon submission is :
* - this.method
* - submitter.data-formMethod (if method is not GET|POST|dialog)
* - submitter.formMethod
* - form.method
*/
@property({attribute:true, type: String})
method ?:string;

/**
* Content Encoding to use
* Supports form-data or application/json
* If not provided, will default to "form.enctype" or "application/json"
* Order of precedence upon submission is :
* - this.encoding
* - submitter.formEnctype
* - submitter.form.enctype
* - submitter.form.encoding (deprecated per-spec)
* - "application/json"
*/
@property({attribute: true, type: String})
encoding ?:"application/x-www-form-urlencoded"|"application/json";
Expand Down Expand Up @@ -65,20 +79,20 @@ export default class SubmitFragment extends LitElement{
}


private async _do_submit(form:HTMLFormElement) :Promise<boolean>{
private async _do_submit(form:HTMLFormElement, submitter?:HTMLElement|HTMLButtonElement|HTMLInputElement) :Promise<boolean>{
this.#c?.abort();
let c = this.#c = new AbortController();
this.active = true;
if(this.status) this.status = undefined;

try{

const action = this.action ?? form.action;
const method = this.method ?? form.method;
const action = this.action || submitter?.["formAction"] || form.action;
let method = this.method ?? form.method;
if(submitter?.dataset.formMethod) method = submitter.dataset.formMethod;
else if(submitter && "formMethod" in submitter && submitter.formMethod) method = submitter.formMethod;
if(!action || !method) throw new Error(`Invalid request : [${method.toUpperCase()}] ${action}`)
const encoding = this.encoding ?? form.enctype ?? form.encoding ?? "application/json";
const encoding = this.encoding || submitter?.["formEnctype"] || form.enctype || form.encoding || "application/json";
const body = this.encode(form, encoding);

let res = await fetch(action, {
method,
body,
Expand Down Expand Up @@ -113,7 +127,7 @@ export default class SubmitFragment extends LitElement{
}
e.preventDefault();
e.stopPropagation();
this._do_submit(form).then((success)=>{
this._do_submit(form, e.submitter).then((success)=>{
if(success) this.dispatchEvent(new CustomEvent("submit", {}));
});
return false;
Expand All @@ -123,12 +137,10 @@ export default class SubmitFragment extends LitElement{
const target = e.target as HTMLSelectElement|HTMLInputElement;
if(!this.submit) return false;
if(this.submit.indexOf(target.name) === -1) return false;
if(!target.form) return console.warn("Element has no associated form : ", target);
if(!target.form) return console.warn("Changed child has no associated form : ", target);
e.preventDefault();
e.stopPropagation();
this._do_submit(target.form).then((success)=>{
if(success) this.dispatchEvent(new CustomEvent("submit", {}));
});
target.form.requestSubmit();
}

protected update(changedProperties: PropertyValues): void {
Expand Down

0 comments on commit dbf7381

Please sign in to comment.