/* ────────────────────────────────────────────────────────────
   sharing.jsx — 공유/멤버 관리(feature b §6.4/§7)
   ShareModal: ① 멤버 탭(목록·역할/페르소나 변경·이메일 초대 → /invite/<token> 복사)
              ② 링크 탭(read|comment 공유 링크 생성 → /share/<token> 복사·만료·해제)
   초대/공유 = COPY-PASTE 링크만(이메일 발송 없음). RAW 토큰은 생성 시 1회만 표시.
   REST: GET/POST/PATCH/DELETE /api/projects/:id/members ·
         GET/POST/DELETE /api/projects/:id/share-links · POST /members(=초대).
   owner 전용 쓰기. window 노출 패턴. tokens·해요체·이모지 없음.
   ──────────────────────────────────────────────────────────── */

const SHARE_ROLE_LABEL = { owner: "소유자", editor: "편집자", reviewer: "검수자", viewer: "뷰어" };
const SHARE_ROLE_OPTS = [
  { v: "viewer",   t: "뷰어 (읽기)" },
  { v: "reviewer", t: "검수자 (코멘트·검수)" },
  { v: "editor",   t: "편집자 (편집)" },
];
const SHARE_PERSONA_OPTS = [
  { v: "",       t: "페르소나 없음" },
  { v: "SME",    t: "SME (1·2차 검수)" },
  { v: "design", t: "설계 검수" },
  { v: "pm",     t: "PM 검수" },
];
const SHARE_SCOPE_LABEL = { read: "읽기 전용", comment: "코멘트 가능" };

/* 절대 URL 만들기 — 복사용. origin + /share|/invite 상대경로. */
function shareAbsUrl(rel) {
  if (!rel) return "";
  try { return location.origin + rel; } catch (e) { return rel; }
}

/* 복사 버튼 + 인라인 URL 박스 — clipboard 실패 시 select 폴백. */
function CopyLinkBox({ url, label }) {
  const [copied, setCopied] = useState(false);
  const inputRef = useRef(null);
  const abs = shareAbsUrl(url);
  const copy = useCallback(async () => {
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(abs);
      } else if (inputRef.current) {
        inputRef.current.select(); document.execCommand("copy");
      }
      setCopied(true); setTimeout(() => setCopied(false), 1800);
    } catch (e) {
      if (inputRef.current) { inputRef.current.select(); }
    }
  }, [abs]);
  return React.createElement("div", null,
    label ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-alt)", marginBottom: 5 } }, label) : null,
    React.createElement("div", { style: { display: "flex", gap: 8 } },
      React.createElement("input", {
        ref: inputRef, readOnly: true, value: abs, onFocus: (e) => e.target.select(),
        style: { flex: 1, minWidth: 0, height: 38, padding: "0 12px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-alt)", color: "var(--label-neutral)", fontSize: 12.5, fontFamily: "var(--font-mono, monospace)" },
      }),
      React.createElement(Button, { kind: copied ? "primary" : "outline", size: "sm", onClick: copy, style: { flexShrink: 0 } },
        copied
          ? React.createElement(React.Fragment, null, React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M20 6 9 17l-5-5" })), "복사됨")
          : React.createElement(React.Fragment, null, React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("rect", { x: 9, y: 9, width: 13, height: 13, rx: 2 }), React.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })), "링크 복사"))));
}

/* 이니셜 아바타 — collabInitials 재사용(있으면). */
function shareInitials(name) {
  if (typeof collabInitials === "function") return collabInitials(name);
  const n = (name || "").trim();
  return n ? n.slice(0, 2).toUpperCase() : "?";
}

/* ── 멤버 탭 ── */
function MembersTab({ project, isOwner, onErr }) {
  const [members, setMembers] = useState(null);
  const [busy, setBusy] = useState(false);
  /* 초대 폼 */
  const [email, setEmail] = useState("");
  const [role, setRole] = useState("reviewer");
  const [persona, setPersona] = useState("");
  const [newInvite, setNewInvite] = useState(null);   // { url, email } | null

  const load = useCallback(async () => {
    try {
      const data = await api(`/projects/${project.id}/members`);
      setMembers(Array.isArray(data?.items) ? data.items : []);
    } catch (ex) { onErr(ex.message || "멤버를 불러오지 못했어요."); setMembers([]); }
  }, [project.id, onErr]);
  useEffect(() => { load(); }, [load]);

  const invite = useCallback(async () => {
    const em = email.trim();
    if (!em) { onErr("이메일을 입력해 주세요."); return; }
    setBusy(true); onErr("");
    try {
      const data = await api(`/projects/${project.id}/members`, {
        method: "POST",
        body: { email: em, role, persona: persona || undefined },
      });
      setNewInvite(data && data.invite ? data.invite : null);
      setEmail("");
      await load();
    } catch (ex) { onErr(ex.message || "초대를 만들지 못했어요."); }
    finally { setBusy(false); }
  }, [project.id, email, role, persona, onErr, load]);

  const changeRole = useCallback(async (userId, patch) => {
    setBusy(true); onErr("");
    try {
      await api(`/projects/${project.id}/members/${userId}`, { method: "PATCH", body: patch });
      await load();
    } catch (ex) { onErr(ex.message || "역할을 바꾸지 못했어요."); }
    finally { setBusy(false); }
  }, [project.id, onErr, load]);

  const removeMember = useCallback(async (userId) => {
    setBusy(true); onErr("");
    try {
      await api(`/projects/${project.id}/members/${userId}`, { method: "DELETE" });
      await load();
    } catch (ex) { onErr(ex.message || "멤버를 제거하지 못했어요."); }
    finally { setBusy(false); }
  }, [project.id, onErr, load]);

  return React.createElement("div", null,
    /* 초대 폼(owner 만) */
    isOwner
      ? React.createElement("div", { style: { background: "var(--bg-normal)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", padding: "14px 16px", marginBottom: 18 } },
          React.createElement("div", { className: "t-label-1", style: { color: "var(--label-neutral)", fontWeight: 800, marginBottom: 10 } }, "초대 링크 만들기"),
          React.createElement("p", { className: "t-caption-1", style: { color: "var(--label-alt)", margin: "0 0 12px", lineHeight: 1.55 } }, "이메일로 발송하지 않고 링크를 만들어요. 만든 링크를 복사해 직접 전달하면, 초대받은 분이 로그인 후 수락해요."),
          React.createElement("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 10 } },
            React.createElement("input", {
              type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "초대할 이메일",
              style: { flex: "1 1 200px", minWidth: 0, height: 38, padding: "0 12px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13.5 },
            }),
            React.createElement("select", {
              value: role, onChange: (e) => setRole(e.target.value),
              style: { height: 38, padding: "0 10px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13 },
            }, SHARE_ROLE_OPTS.map((o) => React.createElement("option", { key: o.v, value: o.v }, o.t))),
            role === "reviewer"
              ? React.createElement("select", {
                  value: persona, onChange: (e) => setPersona(e.target.value),
                  style: { height: 38, padding: "0 10px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13 },
                }, SHARE_PERSONA_OPTS.map((o) => React.createElement("option", { key: o.v, value: o.v }, o.t)))
              : null,
            React.createElement(Button, { kind: "primary", size: "sm", onClick: invite, disabled: busy, style: { flexShrink: 0 } },
              busy ? React.createElement(Spinner, { s: 14, color: "#fff" }) : "초대 링크 생성")),
          newInvite
            ? React.createElement("div", { style: { marginTop: 6, padding: "12px", background: "var(--positive-bg)", border: "1px solid var(--positive)", borderRadius: "var(--r-sm)" } },
                React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-neutral)", marginBottom: 8 } },
                  React.createElement("b", null, newInvite.email), " 님 초대 링크가 만들어졌어요. 이 링크를 복사해 전달해 주세요."),
                React.createElement(CopyLinkBox, { url: newInvite.url }))
            : null)
      : null,

    /* 멤버 목록 */
    React.createElement("div", { className: "t-label-1", style: { color: "var(--label-neutral)", fontWeight: 800, marginBottom: 10 } }, "멤버"),
    members === null
      ? React.createElement(StateMsg, { icon: React.createElement(Spinner, { s: 20 }), title: "불러오는 중이에요" })
      : members.length === 0
        ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-assist)", padding: "8px 2px" } }, "아직 멤버가 없어요.")
        : React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
            members.map((m) => {
              const isThisOwner = m.role === "owner";
              return React.createElement("div", { key: m.userId, style: { display: "flex", alignItems: "center", gap: 11, padding: "10px 12px", border: "1px solid var(--border)", borderRadius: "var(--r-sm)", background: "var(--bg-normal)" } },
                React.createElement("div", { style: { width: 32, height: 32, borderRadius: "50%", flexShrink: 0, background: "var(--primary-light)", color: "var(--primary)", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 800, fontSize: 12 } }, shareInitials(m.name)),
                React.createElement("div", { style: { minWidth: 0, flex: 1 } },
                  React.createElement("div", { className: "t-label-2", style: { color: "var(--label-normal)", fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, m.name || "이름 없음"),
                  m.email ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-assist)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, m.email) : null),
                /* 역할/페르소나 — owner 가 아니고 대상이 owner 가 아니면 편집 가능 */
                (isOwner && !isThisOwner)
                  ? React.createElement("div", { style: { display: "flex", gap: 6, alignItems: "center", flexShrink: 0 } },
                      React.createElement("select", {
                        value: m.role, disabled: busy, onChange: (e) => changeRole(m.userId, { role: e.target.value, persona: e.target.value === "reviewer" ? (m.persona || null) : null }),
                        style: { height: 32, padding: "0 8px", borderRadius: "var(--r-xs)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 12.5 },
                      }, SHARE_ROLE_OPTS.map((o) => React.createElement("option", { key: o.v, value: o.v }, SHARE_ROLE_LABEL[o.v]))),
                      m.role === "reviewer"
                        ? React.createElement("select", {
                            value: m.persona || "", disabled: busy, onChange: (e) => changeRole(m.userId, { persona: e.target.value || null }),
                            style: { height: 32, padding: "0 8px", borderRadius: "var(--r-xs)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 12.5 },
                          }, SHARE_PERSONA_OPTS.map((o) => React.createElement("option", { key: o.v, value: o.v }, o.t)))
                        : null,
                      React.createElement("button", { onClick: () => removeMember(m.userId), disabled: busy, "aria-label": "멤버 제거", style: { ...btnStyle("ghost", "sm"), padding: "0 7px", color: "var(--negative)" } },
                        React.createElement("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }))))
                  : React.createElement("div", { style: { display: "flex", gap: 6, alignItems: "center", flexShrink: 0 } },
                      React.createElement(Pill, { color: "var(--label-neutral)", bg: "var(--bg-alt)" }, SHARE_ROLE_LABEL[m.role] || m.role),
                      m.persona ? React.createElement(Pill, { color: "var(--violet)", bg: "var(--violet-bg)" }, (SHARE_PERSONA_OPTS.find((o) => o.v === m.persona) || {}).t || m.persona) : null));
            }))
  );
}

/* ── 링크 탭(게스트 공유) ── */
function LinksTab({ project, isOwner, onErr }) {
  const [links, setLinks] = useState(null);
  const [busy, setBusy] = useState(false);
  const [scope, setScope] = useState("read");
  const [label, setLabel] = useState("");
  const [days, setDays] = useState(14);
  const [newLink, setNewLink] = useState(null);   // { url, scope } | null

  const load = useCallback(async () => {
    try {
      const data = await api(`/projects/${project.id}/share-links`);
      setLinks(Array.isArray(data?.items) ? data.items : []);
    } catch (ex) { onErr(ex.message || "공유 링크를 불러오지 못했어요."); setLinks([]); }
  }, [project.id, onErr]);
  useEffect(() => { load(); }, [load]);

  const create = useCallback(async () => {
    setBusy(true); onErr("");
    try {
      const d = Math.max(1, Math.min(90, parseInt(days, 10) || 14));
      const data = await api(`/projects/${project.id}/share-links`, {
        method: "POST", body: { scope, label: label.trim() || undefined, expiresInDays: d },
      });
      setNewLink(data && data.shareLink ? data.shareLink : null);
      setLabel("");
      await load();
    } catch (ex) { onErr(ex.message || "공유 링크를 만들지 못했어요."); }
    finally { setBusy(false); }
  }, [project.id, scope, label, days, onErr, load]);

  const revoke = useCallback(async (linkId) => {
    setBusy(true); onErr("");
    try {
      await api(`/projects/${project.id}/share-links/${linkId}`, { method: "DELETE" });
      await load();
    } catch (ex) { onErr(ex.message || "공유 링크를 해제하지 못했어요."); }
    finally { setBusy(false); }
  }, [project.id, onErr, load]);

  if (!isOwner) {
    return React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-assist)", padding: "12px 2px", lineHeight: 1.6 } }, "공유 링크는 소유자만 만들 수 있어요.");
  }

  return React.createElement("div", null,
    React.createElement("div", { style: { background: "var(--bg-normal)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", padding: "14px 16px", marginBottom: 18 } },
      React.createElement("div", { className: "t-label-1", style: { color: "var(--label-neutral)", fontWeight: 800, marginBottom: 10 } }, "공유 링크 만들기"),
      React.createElement("p", { className: "t-caption-1", style: { color: "var(--label-alt)", margin: "0 0 12px", lineHeight: 1.55 } }, "로그인 없이 열 수 있는 게스트 링크예요. 읽기 전용이거나, 코멘트만 남길 수 있어요. 만료 기간은 최대 90일이에요."),
      React.createElement("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 10 } },
        React.createElement("input", {
          type: "text", value: label, onChange: (e) => setLabel(e.target.value), placeholder: "메모 (선택)",
          style: { flex: "1 1 160px", minWidth: 0, height: 38, padding: "0 12px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13.5 },
        }),
        React.createElement("select", {
          value: scope, onChange: (e) => setScope(e.target.value),
          style: { height: 38, padding: "0 10px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13 },
        }, React.createElement("option", { value: "read" }, "읽기 전용"), React.createElement("option", { value: "comment" }, "코멘트 가능")),
        React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 5 } },
          React.createElement("input", {
            type: "number", min: 1, max: 90, value: days, onChange: (e) => setDays(e.target.value),
            style: { width: 64, height: 38, padding: "0 8px", borderRadius: "var(--r-sm)", border: "1px solid var(--border)", background: "var(--bg-canvas)", color: "var(--label-normal)", fontSize: 13.5, textAlign: "right" },
          }),
          React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-alt)" } }, "일")),
        React.createElement(Button, { kind: "primary", size: "sm", onClick: create, disabled: busy, style: { flexShrink: 0 } },
          busy ? React.createElement(Spinner, { s: 14, color: "#fff" }) : "링크 생성")),
      newLink
        ? React.createElement("div", { style: { marginTop: 6, padding: "12px", background: "var(--positive-bg)", border: "1px solid var(--positive)", borderRadius: "var(--r-sm)" } },
            React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-neutral)", marginBottom: 8 } },
              React.createElement("b", null, SHARE_SCOPE_LABEL[newLink.scope] || newLink.scope), " 공유 링크가 만들어졌어요. 이 링크를 복사해 전달해 주세요."),
            React.createElement(CopyLinkBox, { url: newLink.url }))
        : null),

    React.createElement("div", { className: "t-label-1", style: { color: "var(--label-neutral)", fontWeight: 800, marginBottom: 10 } }, "발급된 링크"),
    links === null
      ? React.createElement(StateMsg, { icon: React.createElement(Spinner, { s: 20 }), title: "불러오는 중이에요" })
      : links.length === 0
        ? React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-assist)", padding: "8px 2px" } }, "아직 발급한 공유 링크가 없어요.")
        : React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } },
            links.map((l) => {
              const revoked = !!l.revokedAt;
              const expired = l.expiresAt && new Date(l.expiresAt).getTime() < Date.now();
              return React.createElement("div", { key: l.id, style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", border: "1px solid var(--border)", borderRadius: "var(--r-sm)", background: "var(--bg-normal)", opacity: revoked ? 0.6 : 1 } },
                React.createElement("div", { style: { minWidth: 0, flex: 1 } },
                  React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, marginBottom: 2 } },
                    React.createElement(Pill, { color: l.scope === "comment" ? "var(--primary)" : "var(--label-neutral)", bg: l.scope === "comment" ? "var(--primary-light)" : "var(--bg-alt)" }, SHARE_SCOPE_LABEL[l.scope] || l.scope),
                    l.label ? React.createElement("span", { className: "t-caption-1", style: { color: "var(--label-neutral)", fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, l.label) : null,
                    revoked ? React.createElement(Pill, { color: "var(--negative)", bg: "var(--negative-bg)" }, "해제됨")
                      : expired ? React.createElement(Pill, { color: "var(--caution)", bg: "var(--caution-bg)" }, "만료됨") : null),
                  React.createElement("div", { className: "t-caption-1", style: { color: "var(--label-assist)" } },
                    `조회 ${typeof l.useCount === "number" ? l.useCount : 0}회`,
                    l.expiresAt ? ` · ${fmtShareDate(l.expiresAt)} 만료` : "")),
                !revoked
                  ? React.createElement("button", { onClick: () => revoke(l.id), disabled: busy, style: { ...btnStyle("ghost", "sm"), color: "var(--negative)", flexShrink: 0 } }, "해제")
                  : null);
            }))
  );
}

/* 날짜 포맷 — dashboard fmtDate 와 유사하지만 충돌 회피용 로컬. */
function fmtShareDate(iso) {
  if (!iso) return "";
  try {
    const d = new Date(iso);
    return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, "0")}.${String(d.getDate()).padStart(2, "0")}`;
  } catch (e) { return ""; }
}

/* ── ShareModal — 멤버/링크 탭 셸 ── */
function ShareModal({ project, role, onClose }) {
  const isOwner = (role || project.role || project.projectRole) === "owner";
  const [tab, setTab] = useState("members");
  const [err, setErr] = useState("");

  const title = project.chapterName || project.courseName || "차시";

  return React.createElement("div", {
    style: { position: "fixed", inset: 0, background: "rgba(0,0,0,0.42)", display: "flex", justifyContent: "flex-end", zIndex: 60 },
    onMouseDown: (e) => { if (e.target === e.currentTarget) onClose(); },
  },
    React.createElement("div", { style: { width: "min(560px, 100%)", height: "100%", background: "var(--bg-canvas)", borderLeft: "1px solid var(--border)", boxShadow: "var(--sh-2)", display: "flex", flexDirection: "column" } },
      React.createElement("div", { style: { padding: "16px 20px", borderBottom: "1px solid var(--border)", background: "var(--bg-normal)" } },
        React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } },
          React.createElement("svg", { width: 19, height: 19, viewBox: "0 0 24 24", fill: "none", stroke: "var(--primary)", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }, React.createElement("circle", { cx: 18, cy: 5, r: 3 }), React.createElement("circle", { cx: 6, cy: 12, r: 3 }), React.createElement("circle", { cx: 18, cy: 19, r: 3 }), React.createElement("path", { d: "M8.6 13.5l6.8 4M15.4 6.5l-6.8 4" })),
          React.createElement("div", { className: "t-headline-1", style: { color: "var(--label-normal)", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, `${title} 공유`),
          React.createElement("button", { onClick: onClose, "aria-label": "닫기", style: { ...btnStyle("ghost", "sm"), marginLeft: "auto", padding: "0 8px" } },
            React.createElement("svg", { width: 17, height: 17, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round" }, React.createElement("path", { d: "M18 6 6 18M6 6l12 12" })))),
        React.createElement("div", { style: { display: "flex", gap: 4, marginTop: 14 } },
          [["members", "멤버 초대"], ["links", "공유 링크"]].map(([k, t]) =>
            React.createElement("button", {
              key: k, onClick: () => { setTab(k); setErr(""); },
              style: {
                height: 32, padding: "0 14px", borderRadius: "var(--r-sm)", cursor: "pointer", fontWeight: 700, fontSize: 13,
                border: "1px solid " + (tab === k ? "var(--primary)" : "var(--border)"),
                background: tab === k ? "var(--primary-light)" : "var(--bg-normal)",
                color: tab === k ? "var(--primary)" : "var(--label-neutral)",
              },
            }, t)))),
      React.createElement("div", { style: { flex: 1, overflowY: "auto", padding: "18px 20px 28px" } },
        err ? React.createElement("div", { className: "t-label-2", style: { color: "var(--negative)", background: "var(--negative-bg)", borderRadius: "var(--r-sm)", padding: "9px 13px", marginBottom: 14 } }, err) : null,
        tab === "members"
          ? React.createElement(MembersTab, { project, isOwner, onErr: setErr })
          : React.createElement(LinksTab, { project, isOwner, onErr: setErr }))));
}

Object.assign(window, { ShareModal, MembersTab, LinksTab, CopyLinkBox, shareAbsUrl, SHARE_ROLE_LABEL });
