1. #1

    Registriert seit
    17.12.2011
    Beiträge
    129
    Thanked 66 Times in 46 Posts

    Standard U-Labs Scriptable Widget (iOS)

    Hallo zusammen,


    ich habe ein kleines Scriptable Widget für iOS/iPadOS entwickelt, das die 5 neuesten Beiträge aus dem Forum anzeigt. Mit einem Klick auf einen Beitrag wird man direkt zum entsprechenden Thread weitergeleitet.

    Voraussetzungen:
    - Du benötigst iOS/iPadOS und die App Scriptable (kostenlos im App Store erhältlich).
    - Das Widget funktioniert nur auf Geräten mit iOS/iPadOS 14 oder höher.

    Hinweis:
    Bitte verwendet momentan nur das large Widget, da die anderen Größen noch nicht angepasst wurden. Ich arbeite daran, diese in einer zukünftigen Version zu integrieren.

    Viel Spaß beim Ausprobieren

    Code:
    Spoiler:
    Code:
    // == Scriptable Widget Code ==
    // @name U-Labs Forum Widget
    // @desc Zeigt die neuesten Beiträge aus dem U-Labs-Forum an.
    // @version 1.0
    // @autor Integer
    // @icon newspaper.fill
    
    const forumUrl = "https://u-labs.de/forum/";
    
    const isDarkMode = Device.isUsingDarkAppearance();
    const backgroundColor = isDarkMode ? new Color("#1E1E1E") : Color.white();
    const textColor = isDarkMode ? Color.white() : new Color("#333333");
    const metaTextColor = isDarkMode ? Color.gray() : new Color("#666666");
    const altRowColor = isDarkMode ? new Color("#3C3C3E") : new Color("#E0E0E0");
    const evenRowColor = isDarkMode ? new Color("#2C2C2C") : new Color("#F5F5F5");
    
    async function fetchForumData() {
      try {
        const request = new Request(forumUrl);
        return await request.loadString();
      } catch (error) {
        displayError("Fehler beim Laden der Daten");
        return null;
      }
    }
    
    function displayError(message) {
      const widget = new ListWidget();
      widget.backgroundColor = backgroundColor;
      const errorText = widget.addText(message);
      errorText.textColor = textColor;
      errorText.font = Font.mediumSystemFont(14);
      Script.setWidget(widget);
      Script.complete();
    }
    
    function decodeHtmlEntities(text) {
      const entities = {
        """: '"',
        "&": '&',
        "&lt;": '<',
        "&gt;": '>',
        "'": "'"
      };
      return text.replace(/&[^;]+;/g, match => entities[match] || match);
    }
    
    function parseForumPosts(html) {
      const postRegex = /<tr data-thread-id="(\d+)"[^>]*>[\s\S]*?ulo-post-title[^>]*href="([^"]+)"[^>]*>([^<]+)[\s\S]*?projecthead">([^<]+)[\s\S]*?ulo-post-time[^>]*data-timestamp="(\d+)"[^>]*>([^<]+)<\/span>/g;
      const posts = [];
      
      let match;
      while ((match = postRegex.exec(html)) !== null) {
        const [_, threadId, url, title, author, timestamp, timeAgo] = match;
        posts.push({
          threadId,
          url: `https://u-labs.de/forum/${url}`,
          title: decodeHtmlEntities(title.trim()),
          author: decodeHtmlEntities(author.trim()),
          timestamp,
          timeAgo: decodeHtmlEntities(timeAgo.trim())
        });
      }
      return posts;
    }
    
    function getClickedPosts() {
      let clickedPosts = [];
      const fileManager = FileManager.local();
      const filePath = fileManager.joinPath(fileManager.documentsDirectory(), "clickedPosts.json");
      
      try {
        const fileData = fileManager.readString(filePath);
        clickedPosts = fileData ? JSON.parse(fileData) : [];
      } catch (error) {
        log("Fehler beim Laden der geklickten Beiträge:", error);
      }
    
      return clickedPosts;
    }
    
    function saveClickedPost(threadId) {
      const clickedPosts = getClickedPosts();
      if (!clickedPosts.includes(threadId)) {
        clickedPosts.push(threadId);
        const fileManager = FileManager.local();
        const filePath = fileManager.joinPath(fileManager.documentsDirectory(), "clickedPosts.json");
        try {
          fileManager.writeString(filePath, JSON.stringify(clickedPosts));
        } catch (error) {
          log("Fehler beim Speichern der geklickten Beiträge:", error);
        }
      }
    }
    
    function addMetaStack(metaStack, author, timeAgo) {
      const authorIcon = SFSymbol.named("person.fill");
      const authorImage = metaStack.addImage(authorIcon.image);
      authorImage.tintColor = metaTextColor;
      authorImage.imageSize = new Size(12, 12);
      
      const authorText = metaStack.addText(` ${author} ·`);
      authorText.font = Font.systemFont(12);
      authorText.textColor = metaTextColor;
    
      const timeIcon = SFSymbol.named("clock.fill");
      const timeImage = metaStack.addImage(timeIcon.image);
      timeImage.tintColor = metaTextColor;
      timeImage.imageSize = new Size(12, 12);
      
      const timeText = metaStack.addText(` ${timeAgo}`);
      timeText.font = Font.systemFont(12);
      timeText.textColor = metaTextColor;
    
      metaStack.centerAlignContent();
    }
    
    async function createWidget(size) {
      const htmlData = await fetchForumData();
      if (!htmlData) return;
    
      const posts = parseForumPosts(htmlData);
      const widget = new ListWidget();
      widget.backgroundColor = backgroundColor;
      widget.setPadding(8, 8, 8, 8);
    
      const headerStack = widget.addStack();
      headerStack.layoutHorizontally();
      headerStack.centerAlignContent();
      headerStack.spacing = 8;
      
      const headerIcon = SFSymbol.named("newspaper.fill");
      const headerIconImage = headerStack.addImage(headerIcon.image);
      headerIconImage.tintColor = Color.blue();
      headerIconImage.imageSize = new Size(20, 20);
      
      const headerText = headerStack.addText("Neuste Beiträge");
      headerText.font = Font.boldSystemFont(16);
      headerText.textColor = textColor;
      
      widget.addSpacer(12);
    
      const maxPosts = size === "small" ? 1 : size === "medium" ? 2 : 5;
    
      posts.slice(0, maxPosts).forEach((post, index) => {
        const postBackground = widget.addStack();
        postBackground.layoutVertically();
        postBackground.setPadding(8, 8, 8, 8);
        postBackground.backgroundColor = getClickedPosts().includes(post.threadId) ? new Color("#A0A0A0") : (index % 2 === 0 ? altRowColor : evenRowColor);
        postBackground.cornerRadius = 8;
        postBackground.size = new Size(widget.size ? widget.size.width - 32 : 300, 0);
    
        const postStack = postBackground.addStack();
        postStack.layoutVertically();
    
        const title = postStack.addText(post.title);
        title.font = size === "small" ? Font.boldSystemFont(12) : Font.boldSystemFont(14);
        title.textColor = textColor;
        title.lineLimit = 1;
        title.url = post.url;
        title.onTap = () => {
          saveClickedPost(post.threadId);
          Script.complete();
        };
    
        if (size !== "small") {
          const metaStack = postStack.addStack();
          metaStack.layoutHorizontally();
          metaStack.spacing = 4;
          addMetaStack(metaStack, post.author, post.timeAgo);
        }
    
        widget.addSpacer(8);
      });
    
      return widget;
    }
    
    const widgetSize = config.widgetFamily || "large";
    const widget = await createWidget(widgetSize);
    if (!config.runsInWidget) widget.presentLarge();
    Script.setWidget(widget);
    Script.complete();
    Geändert von Integer (09.11.2024 um 14:28 Uhr)

  2. The Following 3 Users Say Thank You to Integer For This Useful Post:

    Darkfield (10.11.2024), DMW007 (10.11.2024), SeBi (Gestern)

Ähnliche Themen

  1. Antworten: 0
    Letzter Beitrag: 26.03.2021, 18:49
  2. Antworten: 2
    Letzter Beitrag: 10.07.2014, 08:55
  3. [FAQ] U-Labs
    Von Devon im Forum FAQ
    Antworten: 2
    Letzter Beitrag: 23.04.2014, 14:43
  4. Widget nicht für bestimmtes Handy??
    Von ulkhut im Forum Android
    Antworten: 4
    Letzter Beitrag: 12.10.2013, 20:56
  5. [frage] Hd Widget
    Von Skindred im Forum Android
    Antworten: 5
    Letzter Beitrag: 30.07.2012, 11:52
Diese Seite nutzt Cookies, um das Nutzererlebnis zu verbessern. Klicken Sie hier, um das Cookie-Tracking zu deaktivieren.