add: preference value bindings for Zotero 6
add: global variables fix: bootstrap bugs
This commit is contained in:
		
							parent
							
								
									600bda7bd4
								
							
						
					
					
						commit
						7bcbd3e025
					
				
							
								
								
									
										19
									
								
								addon/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								addon/bootstrap.js
									
									
									
									
										vendored
									
									
								
							@ -69,7 +69,15 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
 | 
			
		||||
    rootURI = resourceURI.spec;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const ctx = { Zotero, rootURI };
 | 
			
		||||
  const window = Zotero.getMainWindow();
 | 
			
		||||
  // Global variables for plugin code
 | 
			
		||||
  const ctx = {
 | 
			
		||||
    Zotero,
 | 
			
		||||
    rootURI,
 | 
			
		||||
    window,
 | 
			
		||||
    document: window.document,
 | 
			
		||||
    ZoteroPane: Zotero.getActiveZoteroPane(),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Services.scriptloader.loadSubScript(
 | 
			
		||||
    `${rootURI}/chrome/content/scripts/index.js`,
 | 
			
		||||
@ -86,13 +94,6 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
 | 
			
		||||
      ["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
 | 
			
		||||
      ["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Zotero.PreferencePanes.register({
 | 
			
		||||
    //   pluginID: "__addonID__",
 | 
			
		||||
    //   src: rootURI + "chrome/content/preferences.xhtml",
 | 
			
		||||
    //   extraDTD: ["chrome://__addonRef__/locale/overlay.dtd"],
 | 
			
		||||
    //   defaultXUL: true,
 | 
			
		||||
    // });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -113,8 +114,10 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
 | 
			
		||||
 | 
			
		||||
  Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
 | 
			
		||||
 | 
			
		||||
  if (chromeHandle) {
 | 
			
		||||
    chromeHandle.destruct();
 | 
			
		||||
    chromeHandle = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function uninstall(data, reason) {}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import Addon from "./addon";
 | 
			
		||||
import AddonModule from "./module";
 | 
			
		||||
import { addonName } from "../package.json";
 | 
			
		||||
import { addonName, addonID, addonRef } from "../package.json";
 | 
			
		||||
 | 
			
		||||
class AddonEvents extends AddonModule {
 | 
			
		||||
  private notifierCallback: any;
 | 
			
		||||
@ -28,15 +28,12 @@ class AddonEvents extends AddonModule {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async onInit(_Zotero: _ZoteroConstructable, rootURI) {
 | 
			
		||||
    this._Addon.Zotero = _Zotero;
 | 
			
		||||
  public async onInit() {
 | 
			
		||||
    this._Addon.Zotero = Zotero;
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    this._Addon.rootURI = rootURI;
 | 
			
		||||
    // This function is the setup code of the addon
 | 
			
		||||
    this._Addon.Utils.Tool.log(`${addonName}: init called`);
 | 
			
		||||
    // alert(112233);
 | 
			
		||||
 | 
			
		||||
    // Reset prefs
 | 
			
		||||
    this.resetState();
 | 
			
		||||
 | 
			
		||||
    // Register the callback in Zotero as an item observer
 | 
			
		||||
    let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
 | 
			
		||||
@ -54,26 +51,41 @@ class AddonEvents extends AddonModule {
 | 
			
		||||
      false
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Initialize preference window
 | 
			
		||||
    this.initPrefs();
 | 
			
		||||
    this._Addon.views.initViews();
 | 
			
		||||
    this._Addon.views.initPrefs();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private resetState(): void {
 | 
			
		||||
    /* 
 | 
			
		||||
      For prefs that could be simply set to a static default value,
 | 
			
		||||
      Please use addon/defaults/preferences/defaults.js
 | 
			
		||||
      Reset other preferrences here.
 | 
			
		||||
      Uncomment to use the example code.
 | 
			
		||||
    */
 | 
			
		||||
    // let testPref = Zotero.Prefs.get("addonTemplate.testPref");
 | 
			
		||||
    // if (typeof testPref === "undefined") {
 | 
			
		||||
    //   Zotero.Prefs.set("addonTemplate.testPref", true);
 | 
			
		||||
    // }
 | 
			
		||||
  public initPrefs() {
 | 
			
		||||
    this._Addon.Utils.Tool.log(this._Addon.rootURI);
 | 
			
		||||
    const prefOptions = {
 | 
			
		||||
      pluginID: addonID,
 | 
			
		||||
      src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
 | 
			
		||||
      label: "Template",
 | 
			
		||||
      image: `chrome://${addonRef}/content/icons/favicon.png`,
 | 
			
		||||
      extraDTD: [`chrome://${addonRef}/locale/overlay.dtd`],
 | 
			
		||||
      defaultXUL: true,
 | 
			
		||||
      onload: (win: Window) => {
 | 
			
		||||
        this._Addon.prefs.initPreferences(win);
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    if (this._Addon.Utils.Compat.isZotero7()) {
 | 
			
		||||
      Zotero.PreferencePanes.register(prefOptions);
 | 
			
		||||
    } else {
 | 
			
		||||
      this._Addon.Utils.Compat.registerPrefPane(prefOptions);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private unInitPrefs() {
 | 
			
		||||
    if (!this._Addon.Utils.Compat.isZotero7()) {
 | 
			
		||||
      this._Addon.Utils.Compat.unregisterPrefPane();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public onUnInit(): void {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
 | 
			
		||||
    this.unInitPrefs();
 | 
			
		||||
    //  Remove elements and do clean up
 | 
			
		||||
    this._Addon.views.unInitViews();
 | 
			
		||||
    // Remove addon object
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/index.ts
									
									
									
									
									
								
							@ -1,7 +1,17 @@
 | 
			
		||||
import Addon from "./addon";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Globals: bootstrap.js > ctx
 | 
			
		||||
 * const ctx = {
 | 
			
		||||
    Zotero,
 | 
			
		||||
    rootURI,
 | 
			
		||||
    window,
 | 
			
		||||
    document: window.document,
 | 
			
		||||
    ZoteroPane: Zotero.getActiveZoteroPane(),
 | 
			
		||||
  };
 | 
			
		||||
 */
 | 
			
		||||
if (!Zotero.AddonTemplate) {
 | 
			
		||||
  Zotero.AddonTemplate = new Addon();
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  Zotero.AddonTemplate.events.onInit(Zotero, rootURI);
 | 
			
		||||
  Zotero.AddonTemplate.events.onInit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										156
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								src/utils.ts
									
									
									
									
									
								
							@ -18,6 +18,9 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
        }
 | 
			
		||||
        return Zotero;
 | 
			
		||||
      },
 | 
			
		||||
      getWindow: () => {
 | 
			
		||||
        return this.Compat.getZotero().getMainWindow() as Window;
 | 
			
		||||
      },
 | 
			
		||||
      // Check if it's running on Zotero 7 (Firefox 102)
 | 
			
		||||
      isZotero7: () => Zotero.platformMajorVersion >= 102,
 | 
			
		||||
      // Firefox 102 support DOMParser natively
 | 
			
		||||
@ -33,7 +36,12 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
          ].createInstance(Components.interfaces.nsIDOMParser);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      isXULElement: (elem: Element) => {
 | 
			
		||||
        return (
 | 
			
		||||
          elem.namespaceURI ===
 | 
			
		||||
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      // create XUL element
 | 
			
		||||
      createXULElement: (doc: Document, type: string) => {
 | 
			
		||||
        if (this.Compat.isZotero7()) {
 | 
			
		||||
@ -93,6 +101,145 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
      },
 | 
			
		||||
      prefPaneCache: { win: undefined, listeners: [], ids: [] },
 | 
			
		||||
      registerPrefPane: (options: PrefPaneOptions) => {
 | 
			
		||||
        const _initImportedNodesPostInsert = (container) => {
 | 
			
		||||
          const _observerSymbols = new Map();
 | 
			
		||||
          const Zotero = this.Compat.getZotero();
 | 
			
		||||
          const window = container.ownerGlobal;
 | 
			
		||||
          let useChecked = (elem) =>
 | 
			
		||||
            (elem instanceof window.HTMLInputElement &&
 | 
			
		||||
              elem.type == "checkbox") ||
 | 
			
		||||
            elem.tagName == "checkbox";
 | 
			
		||||
 | 
			
		||||
          let syncFromPref = (elem, preference) => {
 | 
			
		||||
            let value = Zotero.Prefs.get(preference, true);
 | 
			
		||||
            if (useChecked(elem)) {
 | 
			
		||||
              elem.checked = value;
 | 
			
		||||
            } else {
 | 
			
		||||
              elem.value = value;
 | 
			
		||||
            }
 | 
			
		||||
            elem.dispatchEvent(new window.Event("syncfrompreference"));
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // We use a single listener function shared between all elements so we can easily detach it later
 | 
			
		||||
          let syncToPrefOnModify = (event) => {
 | 
			
		||||
            if (event.currentTarget.getAttribute("preference")) {
 | 
			
		||||
              let value = useChecked(event.currentTarget)
 | 
			
		||||
                ? event.currentTarget.checked
 | 
			
		||||
                : event.currentTarget.value;
 | 
			
		||||
              Zotero.Prefs.set(
 | 
			
		||||
                event.currentTarget.getAttribute("preference"),
 | 
			
		||||
                value,
 | 
			
		||||
                true
 | 
			
		||||
              );
 | 
			
		||||
              event.currentTarget.dispatchEvent(
 | 
			
		||||
                new window.Event("synctopreference")
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let attachToPreference = (elem, preference) => {
 | 
			
		||||
            Zotero.debug(
 | 
			
		||||
              `Attaching <${elem.tagName}> element to ${preference}`
 | 
			
		||||
            );
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            let symbol = Zotero.Prefs.registerObserver(
 | 
			
		||||
              preference,
 | 
			
		||||
              () => syncFromPref(elem, preference),
 | 
			
		||||
              true
 | 
			
		||||
            );
 | 
			
		||||
            _observerSymbols.set(elem, symbol);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          let detachFromPreference = (elem) => {
 | 
			
		||||
            if (_observerSymbols.has(elem)) {
 | 
			
		||||
              Zotero.debug(
 | 
			
		||||
                `Detaching <${elem.tagName}> element from preference`
 | 
			
		||||
              );
 | 
			
		||||
              // @ts-ignore
 | 
			
		||||
              Zotero.Prefs.unregisterObserver(this._observerSymbols.get(elem));
 | 
			
		||||
              _observerSymbols.delete(elem);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // Activate `preference` attributes
 | 
			
		||||
          for (let elem of container.querySelectorAll("[preference]")) {
 | 
			
		||||
            let preference = elem.getAttribute("preference");
 | 
			
		||||
            if (
 | 
			
		||||
              container.querySelector("preferences > preference#" + preference)
 | 
			
		||||
            ) {
 | 
			
		||||
              Zotero.warn(
 | 
			
		||||
                "<preference> is deprecated -- `preference` attribute values " +
 | 
			
		||||
                  "should be full preference keys, not <preference> IDs"
 | 
			
		||||
              );
 | 
			
		||||
              preference = container
 | 
			
		||||
                .querySelector("preferences > preference#" + preference)
 | 
			
		||||
                .getAttribute("name");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            attachToPreference(elem, preference);
 | 
			
		||||
 | 
			
		||||
            elem.addEventListener(
 | 
			
		||||
              this.Compat.isXULElement(elem) ? "command" : "input",
 | 
			
		||||
              syncToPrefOnModify
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Set timeout before populating the value so the pane can add listeners first
 | 
			
		||||
            window.setTimeout(() => {
 | 
			
		||||
              syncFromPref(elem, preference);
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          new window.MutationObserver((mutations) => {
 | 
			
		||||
            for (let mutation of mutations) {
 | 
			
		||||
              if (mutation.type == "attributes") {
 | 
			
		||||
                let target = mutation.target as Element;
 | 
			
		||||
                detachFromPreference(target);
 | 
			
		||||
                if (target.hasAttribute("preference")) {
 | 
			
		||||
                  attachToPreference(target, target.getAttribute("preference"));
 | 
			
		||||
                  target.addEventListener(
 | 
			
		||||
                    this.Compat.isXULElement(target) ? "command" : "input",
 | 
			
		||||
                    syncToPrefOnModify
 | 
			
		||||
                  );
 | 
			
		||||
                }
 | 
			
		||||
              } else if (mutation.type == "childList") {
 | 
			
		||||
                for (let node of mutation.removedNodes) {
 | 
			
		||||
                  detachFromPreference(node);
 | 
			
		||||
                }
 | 
			
		||||
                for (let node of mutation.addedNodes) {
 | 
			
		||||
                  if (
 | 
			
		||||
                    node.nodeType == Node.ELEMENT_NODE &&
 | 
			
		||||
                    (node as Element).hasAttribute("preference")
 | 
			
		||||
                  ) {
 | 
			
		||||
                    attachToPreference(
 | 
			
		||||
                      node,
 | 
			
		||||
                      (node as Element).getAttribute("preference")
 | 
			
		||||
                    );
 | 
			
		||||
                    node.addEventListener(
 | 
			
		||||
                      this.Compat.isXULElement(node as Element)
 | 
			
		||||
                        ? "command"
 | 
			
		||||
                        : "input",
 | 
			
		||||
                      syncToPrefOnModify
 | 
			
		||||
                    );
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }).observe(container, {
 | 
			
		||||
            childList: true,
 | 
			
		||||
            subtree: true,
 | 
			
		||||
            attributeFilter: ["preference"],
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          // parseXULToFragment() doesn't convert oncommand attributes into actual
 | 
			
		||||
          // listeners, so we'll do it here
 | 
			
		||||
          for (let elem of container.querySelectorAll("[oncommand]")) {
 | 
			
		||||
            elem.oncommand = elem.getAttribute("oncommand");
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          for (let child of container.children) {
 | 
			
		||||
            child.dispatchEvent(new window.Event("load"));
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        const windowListener = {
 | 
			
		||||
          onOpenWindow: (xulWindow) => {
 | 
			
		||||
            const win: Window = xulWindow
 | 
			
		||||
@ -107,7 +254,8 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
                ) {
 | 
			
		||||
                  this.Tool.log("registerPrefPane:detected", options);
 | 
			
		||||
                  const Zotero = this.Compat.getZotero();
 | 
			
		||||
                  options.id || (options.id = `plugin-${new Date().getTime()}`);
 | 
			
		||||
                  options.id ||
 | 
			
		||||
                    (options.id = `plugin-${Zotero.Utilities.randomString()}-${new Date().getTime()}`);
 | 
			
		||||
                  const contenrOrXHR = await Zotero.File.getContentsAsync(
 | 
			
		||||
                    options.src
 | 
			
		||||
                  );
 | 
			
		||||
@ -115,7 +263,7 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
                    typeof contenrOrXHR === "string"
 | 
			
		||||
                      ? contenrOrXHR
 | 
			
		||||
                      : (contenrOrXHR as any as XMLHttpRequest).response;
 | 
			
		||||
                  const src = `<prefpane id="${
 | 
			
		||||
                  const src = `<prefpane xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="${
 | 
			
		||||
                    options.id
 | 
			
		||||
                  }" insertafter="zotero-prefpane-advanced" label="${
 | 
			
		||||
                    options.label || options.pluginID
 | 
			
		||||
@ -136,6 +284,8 @@ class AddonUtils extends AddonModule {
 | 
			
		||||
                  this.Compat.prefPaneCache.win = win;
 | 
			
		||||
                  this.Compat.prefPaneCache.listeners.push(windowListener);
 | 
			
		||||
                  this.Compat.prefPaneCache.ids.push(options.id);
 | 
			
		||||
                  // Binding preferences
 | 
			
		||||
                  _initImportedNodesPostInsert(prefPane);
 | 
			
		||||
                  if (options.onload) {
 | 
			
		||||
                    options.onload(win);
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								src/views.ts
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/views.ts
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
import Addon from "./addon";
 | 
			
		||||
import AddonModule from "./module";
 | 
			
		||||
const { addonRef, addonID } = require("../package.json");
 | 
			
		||||
const { addonRef } = require("../package.json");
 | 
			
		||||
 | 
			
		||||
class AddonViews extends AddonModule {
 | 
			
		||||
  // You can store some element in the object attributes
 | 
			
		||||
@ -16,7 +16,6 @@ class AddonViews extends AddonModule {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public initViews() {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    // You can init the UI elements that
 | 
			
		||||
    // cannot be initialized with overlay.xul
 | 
			
		||||
    this._Addon.Utils.Tool.log("Initializing UI");
 | 
			
		||||
@ -59,34 +58,9 @@ class AddonViews extends AddonModule {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public initPrefs() {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    this._Addon.Utils.Tool.log(this._Addon.rootURI);
 | 
			
		||||
    const prefOptions = {
 | 
			
		||||
      pluginID: addonID,
 | 
			
		||||
      src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
 | 
			
		||||
      label: "Template",
 | 
			
		||||
      image: `chrome://${addonRef}/content/icons/favicon.png`,
 | 
			
		||||
      extraDTD: [`chrome://${addonRef}/locale/overlay.dtd`],
 | 
			
		||||
      defaultXUL: true,
 | 
			
		||||
      onload: (win: Window) => {
 | 
			
		||||
        this._Addon.prefs.initPreferences(win);
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    if (this._Addon.Utils.Compat.isZotero7()) {
 | 
			
		||||
      Zotero.PreferencePanes.register(prefOptions);
 | 
			
		||||
    } else {
 | 
			
		||||
      this._Addon.Utils.Compat.registerPrefPane(prefOptions);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public unInitViews() {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    this._Addon.Utils.Tool.log("Uninitializing UI");
 | 
			
		||||
    this._Addon.Utils.UI.removeAddonElements();
 | 
			
		||||
    if (!this._Addon.Utils.Compat.isZotero7()) {
 | 
			
		||||
      this._Addon.Utils.Compat.unregisterPrefPane();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public showProgressWindow(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								typing/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								typing/global.d.ts
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,9 @@
 | 
			
		||||
declare interface ZoteroCompat {
 | 
			
		||||
  getZotero: () => _ZoteroConstructable;
 | 
			
		||||
  getWindow: () => Window;
 | 
			
		||||
  isZotero7: () => boolean;
 | 
			
		||||
  getDOMParser: () => DOMParser;
 | 
			
		||||
  isXULElement: (elem: Element) => boolean;
 | 
			
		||||
  createXULElement: (doc: Document, type: string) => XUL.Element;
 | 
			
		||||
  parseXHTMLToFragment: (
 | 
			
		||||
    str: string,
 | 
			
		||||
@ -29,7 +31,7 @@ declare interface ZoteroUI {
 | 
			
		||||
  createElement: (
 | 
			
		||||
    doc: Document,
 | 
			
		||||
    tagName: string,
 | 
			
		||||
    namespace: "html" | "svg" | "xul"
 | 
			
		||||
    namespace?: "html" | "svg" | "xul"
 | 
			
		||||
  ) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
 | 
			
		||||
  removeAddonElements: () => void;
 | 
			
		||||
  creatElementsFromJSON: (
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user